Przechwytywanie i modyfikacja klawiszy wysyłanych do aplikacji

0

Rzadko pytam, ale cóż, może tym razem znajdzie się ktoś kto będzie w stanie mi pomóc.

Wpadłem na pomysł, aby przechwytywać wysyłane do Opery klawisze i przerabiać je tak, żebym pisał jak pokemony (przydatne do trollingu). Ale ponieważ lubię się przy okazji czegoś nauczyć, postanowiłem że sam sobie napiszę program który będzie przechwytywać input wysyłany do Opery, przerabiać go zgodnie z moimi wytycznymi i przekazywać dalej.

Analizę problemu rozpocząłem od użycia mojego ulubionego API Loggera do wykrycia co takiego Opera używa do zbierania inputa.
Odkryłem dwie potencjalne funkcje GetKeyboardState i MapVirtualKeyW. Niestety, zpatchowanie którejkolwiek z nich nie dawało oczekiwanego efektu zmiany klawiszy wykrywanych przez Operę.

Po zastanowieniu stwierdziłem że Opera musi odbierać eventy, które zawierają naciśnięcia klawiszy. Więc moim następnym celem były hooki. Zdecydowałem się założyć hook na WH_KEYBOARD na poziomie procesu. Niestety, mimo wielu prób, albo nie udaje się założyć hooka, albo mimo że hook jest założony poprawnie moja procka nie jest nigdy wykonywana.
Oto kod testowy którego używam teraz:

library haxlib;

{$mode objfpc}{$H+}

uses
  Classes,windows,sysutils,JwaPsApi,JwaTlHelp32;

procedure Log(s:ansistring);
var
  f:text;
begin
  assign(f,'X:\porno\najlepsze\log.txt');
  {$I-}append(f);
  if IOResult<>0 then {$I+}rewrite(f);
  writeln(f,s);
  close(f);
end;

var
  hookid:HHOOK;
function KeyProc(code:Integer;wparam:WPARAM;lparam:LPARAM):LRESULT;stdcall;
begin
      Log('Hook call: '+inttostr(wparam)+#32+inttostr(code)+#32+inttostr(lparam));
      case wparam of
        $30:wparam:=$31;
      end;
  result:=CallNextHookEx(hookid,code,wparam,lparam);
end;

function GetTthreadsList(PID:Cardinal): integer;
  var
    SnapProcHandle: THandle;
    NextProc      : Boolean;
    TThreadEntry  : TThreadEntry32;
  begin
    SnapProcHandle := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); //Takes a snapshot of the all threads
      try
        TThreadEntry.dwSize := SizeOf(TThreadEntry);
        NextProc := Thread32First(SnapProcHandle, TThreadEntry);//get the first Thread
        while NextProc do
        begin

          if TThreadEntry.th32OwnerProcessID = PID then //Check the owner Pid against the PID requested
          begin
              Log('Resultthread: '+inttostr(TThreadEntry.th32ThreadID));
              exit(TThreadEntry.th32ThreadID);
          end;

          NextProc := Thread32Next(SnapProcHandle, TThreadEntry);//get the Next Thread
        end;
      finally
        CloseHandle(SnapProcHandle);//Close the Handle
      end;
  end;



begin
  Log('Initialized... hooking');
  hookid:=SetWindowsHookEx(WH_KEYBOARD,@KeyProc,0,GetTthreadsList(GetCurrentProcessId));
  Log('Hooked: '+inttostr(GetLastError)+#32+inttostr(hookid));
end.

[Kod kompilowany pod Lazarusem 1.0.2 z FPC 2.6.0, injector: Process Hacker 2]
Należy się wyjaśnienie funkcji GetTthreadsList - zwraca ona ThreadID maina Opery. Cały kod jest biblioteką która jest injectowana do Opery.
Jak już napisałem, podana wyżej metoda nie daje efektów, ponieważ mimo wielu prób albo nie udaje się założyć hooka albo KeyProc nigdy nie jest wywoływane.

Więc moje pytania:
1.Czemu podana przeze mnie metoda nie działa? Jak ją poprawić?
2.Jak najlepiej przechwytywać i modyfikować input danego procesu (najlepiej bez hooków na poziomie całego systemu)? Chodzi o rozwiązanie dobre do większości procesów i jednocześnie nietoksyczne dla całego OS.

0

Nie bawiłem się w robienie dllki do injekcji pod Lazarusem, bo mało czasu i jakoś mnie "boli" ta próba przerzucenia się na chwile z Delphi 7 na te IDE. Ale do rzeczy. Do tego posta dołączyłem mój tool napisany kiedyś w WinAPI w celu samodoskonalenia. I ku mojemu zaskoczeniu pomimo użycia modułu afxcodehook.pas, który niestety nie kompiluje się pod Lazarusem, o czym już pisałem kiedyś w innym wątku w tym dziale, zainjectował on 32 bitową dllkę w proces Opery, którą na 100000% mam w wersji 64 bitowej. Później udało mi się ustalić że przechwytywane są również komunikaty WM_KEYPRESS. Dlatego jeżeli program koniecznie nie musi być w Lazarusie lub nie musisz injectowana dllka nie musi zakładac Hooka. To ja bym się "podpiął" pod funkcję obsługi komunikatów okna głownego (w zasadzie to Opera jest tak cwanie napisana, że jej dowolny fragment okna jest jako jedna klasa i jeden uchwyt) i podmieniał przy wystąpieniu żądanego komunikatu, wartości LParam czy też inaczej reagował. Metoda może i inwazyjna, ale nie zakładasz żadnego hooka, a ponieważ wstrzyknięta dllka jest uważana przez proces na którym dokonano zastrzyku - za własny kod to pozwoli wykonywać SetWindowLong z parametrem GWL_WNDPROC. Powinno się to udać zrobić również pod Lazarusem, skoro masz zewnętrzny injector, który bez problemu pozwoli Tobie strzyknąć swoją dllkę w dowolny proces. Bo mój dołaczony program robi to przy użyciu wspomnianego modułu, a sama 32 bitowa dllka "siedzi" w zasobach. Dodam tylko, że jeżeli dllka ma być cały czas aktywna to można zrobić moim sprawdzonym sposobem. Ponieważ miałem problem z tworzeniem ukrytego okna przez CreateWindow(Ex) aby dllka "wisiała" jako tak proces, a nie się kończyła, ale tak to nie chciało działać. Można utworzyć pusty zasób na przykład o nazwie a_spy_dialog.rco takiej zawartości:

#define IDC_EMPTYDLG 1000

IDC_EMPTYDLG DIALOGEX 0, 0, 0, 0
WS_EX_TOOLWINDOW
FONT 8, "MS Sans Serif", 400, 0
BEGIN
END

A później w dllce zrobić coś w stylu:

//...
var
  Msg : TMsg;
  DlgH : HWND;
begin
  HMutex := OpenMutex(MUTEX_MODIFY_STATE, False, Mutex_Name);
  if HMutex <> 0 then
  begin
    CloseHandle(HMutex);
    Exit;
  end;
  HMutex := CreateMutex(nil, False, Mutex_Name);
  DlgH := CreateDialog(hInstance, MAKEINTRESOURCE(IDC_EMPTYDLG), 0, @HiddenDlgProc);
  ShowWindow(DlgH, SW_SHOWNOACTIVATE);
  while GetMessage(Msg, 0, 0, 0) do
  begin
    DispatchMessage(Msg);
  end;
end.

Oczywiście w fukcji obsługi komunikatów (tutaj HiddenDlgProc) obsłużyć co trzeba. Można też zrobić na przykład małe okienko dialogowe albo jakiś globalny HotKey o egzotycznej kombinacji, którym można było by uaktywniać lub wyłączać Twoje "pokemon mode" :) Tyle mogę doradzić od siebie. Myślę, że tak doświadczony programista jak Ty, powinien ogarnąć temat.

EDIT: jednak mam 32 bitową Operę, tak się dziwnie pobiera ze strony Opery, bo wolę ją mieć zainstalowaną przez setup a nie moduł aktualizacyjny. Ale to nie problem, bo nawet jeżeli masz swoją Operę w wersji 64 bitowej, a ten injector pozwala wniknąć w proces 64 bitowy, to skompilujesz sobie dllkę pod 64 bitowym Lazatusem i nie będzie problemu. Tylko wtedy mój process spy nie zadziała.

0

A co byś powiedział na ugryzienie tego problemu od innej strony, tzn. skrypt greasemonkey? O wiele prostsze i ładniejsze rozwiązanie.

0
szopenfx napisał(a):

A co byś powiedział na ugryzienie tego problemu od innej strony, tzn. skrypt greasemonkey? O wiele prostsze i ładniejsze rozwiązanie.

Chcę mieć metodę która pozwoli na łapanie dowolnych okien. Głównie chce się podszkolić w WinApi.

@olesio , udało mi się przejąć WNDProc za pomocą SetWindowLong(...,GWL_WNDPROC,...). Niestety póki co mimo że procedurka obsługi wygląda tak:

function WNDProc(h:HWND;msg:UINT;wp:WPARAM;lp:LPARAM):LRESULT;
begin
  Log('Hook call: '+inttostr(wp)+#32+inttostr(msg)+#32+inttostr(lp));
  result:=CallWindowProc(oldwnd,h,msg,wp,lp);
end; 

To Opera losowo się zawiesza, czasami jest to po pierwszym zalogowanym wywołaniu, czasami po n-tym. Ale póki co przeprowadziłem tylko 2 testy, będę kombinował jak zachować stabilność...

0

To nie wiem co jest nie tak. Jak sprawdzisz mój program co dołączyłem do pierwszego posta, to on przechwytuje z Opery 32 bitowej komunikaty, tak jak trzeba i pokazuje to w ListBoxie, a naciskanie klawiszy się nie zawiesza ani nie "wykrzacza". Być może coś nie tak zrobiłeś przy przechwytywaniu komunikatów. Ale na oko wygląda ok, zresztą taką osobę jak Ty nie podejrzewał bym o to że wcześniej, dla pewności nie sprawdziła sobie co i jak na MSDNie albo nie sprawdziła gotowych kodów. Chociażby mojego trainera do Maxa Payna, dostępnego z kodem źródłowym na moim blogu.

0

Odświeżam temat. Nie mogąc spać i widząć, że @-321oho nic nowego nie pisze, stwierdziłem - obym się mylił - że problem go przerósł (?!). I pokombinowałem trochę. Efekty mojej pracy ze źródłami i wszystkimi potrzebnymu plikami są w załączonym do tego posta archiwum. Testowane na najnowszej stabilnej i publicznej Operze 32 bitowej. Działa ok. Sprawia, że pewne litery bez względu na wielkośc podczas wciskania zamieniane są na H4x0r5ki3 odpowiedniki. Kompilowane oczywiście pod Delphi 7 Personal. Pod Lazarusem możesz tę dllkę trzymać poza zasobami i injectować własnym injectorem o ile taki napisałeś pod Lazarusem 64 bitowym albo korzystać z innego. Pisząc to napotkałem tylko na dwa problemy. Pierwszy to sposób Hookowania WH_KEYBOARD - pomogł myk z GetWindowThreadProcessId(PidToHWND(GetCurrentProcessId)) jako ostatnim parametrem. Drugi problem jaki napotkałem to injekcja dll - jeżeli proces opera.exe - musi zostać uruchomiony ze znalezionej w rejestrze ścieżki. Otóż wywołanie Hooka w wątku w samej dllce po stwierdzeniu obecności stworzonego okna głownego nie działało. Działało tylko jeśli Opera była już uruchomiona. Z tego względu zrobiłem odczekiwanie przed injekcją nieco brzydkim Sleepem poza wątkiem. I to pomogło. Może też i enumerowanie okien jest mało eleganckie, ale nie istnieją chyba inne metody aby stwierdzić czy proces jest w stanie, który pozwoli na skuteczną injekcje. Zresztą przy obecnych mocach procesórów przeszukanie nawet kilkuset procesów i okien to chwilka. Oczywiście porządne AntyWirusy powinny milczeć, na przykład KAV 2010 milczy i nie ostrzega, bo w końcu sam Hook nie jest tutaj LowLevelowy ani globalny tylko przechwytuje klawisze w obrębie aplikacji. Można by, ale to już pozostawiam do zabawy autorowi wątku lub inynm zainteresowanym, sprawdzić czy da się założyć takiego nie LowLevelowego Hooka na obcy proces, a nie własny. Mi się tego już nie chciało próbować. Podejrzewam, że mogą być problemy. Chociaz w gotowcu umieszczonym pod tym adresem http://www.swissdelphicenter.ch/torry/showcode.php?id=1722 widać, że można tak kombinować. Jednak ja wolę oczywiście moje sprawdzone i działające na pewno rozwiązanie. Ok, pora iśc trochę pospać :)

0

Nie mogąc spać i widząć, że @-321oho nic nowego nie pisze, stwierdziłem - obym się mylił - że problem go przerósł (?!)

Nie, miałem trochę mało czasu a nie chciałem zapychać tematu mało wnoszącymi odpowiedziami których nawet nie będę mógł zedytować.

Generalnie to udało mi się dojść do tego, że Opera się nie kraszowała, ja dostawałem wszystkie komunikaty, łącznie te z klawiszami, ale mimo że je modyfikowałem przed przekazaniem dalej, Opera nie dawała się zmylić.

Pierwszy to sposób Hookowania WH_KEYBOARD

Ja podmieniałem WNDProc. Widać niepotrzebnie słuchałem olesia, który polecał zmianę WNDProc (?) :D .

Generalnie to osobiście doszedłem do czegoś takiego:

var
  findh:hwnd;
function EnumWindowsCallBack(Handle: hWnd;l:longint): BOOL; stdcall;

var
  PID, hProcess: DWORD;
  Len: Byte;
begin
  Result := True;
  GetWindowThreadProcessId(Handle, PID);
  if PID=GetCurrentProcessId then
    begin
        result:=false;
        findh:=handle;
    end;
end;
function GetMainWindow:hwnd;
begin
  EnumWindows(@EnumWindowsCallBack,0);
  result:=findh;
end;

var
  oldwnd:WNDPROC;

function MyWNDProc(h:HWND;msg:UINT;wp:WPARAM;lp:LPARAM):LRESULT;stdcall;
begin
  Log('Hook call: '+inttostr(wp)+#32+inttostr(msg)+#32+inttostr(lp));
  if (msg=WM_KEYDOWN)or(msg=WM_KEYUP) then
    begin

        Log('KEYKEYKEY: '+inttostr(wp)+#32+inttostr(msg)+#32+inttostr(lp));
        case wp of
        $30:wp:=$31;
          end;
        Log('KEY2KEY2KEY2: '+inttostr(wp)+#32+inttostr(msg)+#32+inttostr(lp));

    end;
  result:=CallWindowProc(oldwnd,h,msg,wp,lp);
end;

function  ThreadStart(parameter : pointer) : ptrint;
var
    msg:TMSG;
      DlgH:HWND;
begin
  Log('Thread initialized.');
  Log('Getting main window.');
    DlgH:=GetMainWindow;
  Log('Main window: '+inttostr(DlgH)+#32+inttostr(GetLastError));
  oldwnd:=windows.WNDPROC(SetWindowLong(dlgh,GWL_WNDPROC,PtrInt(@MyWNDProc)));
  Log('Set new WND: '+inttostr(integer(oldwnd))+#32+inttostr(GetLastError));
  while GetMessage(msg,0,0,0) do
     begin
       TranslateMessage(msg);
       DispatchMessage(msg);
     end;
  Log('Thread exit.');
end;

begin
  Log('Initialized... starting thread');
  BeginThread(@ThreadStart);
  Log('Exiting');
end.

Chętnie się dowiem co spierniczyłem :P (kod ma podmieniać cyfrę 0 na 1) . Trzeba tu jeszcze zauważyć że ten kod nie będzie działać poprawnie pod FPC 2.6.0 ani 2.7.1, z powodu bugu który zgłosiłem, ale bodaj nie jest on jeszcze rozwiązany, więc użyłem swojego, spatchowanego kompilatora (patch już ma z parę miesięcy, ale wciąż powinien działać, załączam).
@olesio , szkoda że nie dałeś mi się pobawić :P . W każdym razie dzięki, pobawię się kodem. Jak możesz to jeszcze wskaż co u mnie jest zrobione źle :P .

0

Po małej bitwie z kodem olesia, który był pisany tak, żeby na Lazarusie nie działał, udało mi się to trochę przerobić.
W paczce którą wrzucam jest następujące:

  1. Źródło głównego modułu który powinien kompilować się pod FPC (uwaga! wymagany patch który wcześniej wspomniałem).
  2. Ponieważ nie każdy ma 50 wersji FPC na kompie - skompilowana biblioteka (za pomocą FPC 2.7.1 sprzed paru miesięcy z moim patchem).
  3. Źródło prostego injectora który można przerobić tak żeby wyszukiwał proces Opery i tam injectował plik (domyślnie odpala plik podany jako pierwszy parametr i injectuje bibliotekę podaną jako drugi parametr).

M1ł3g0 tr0ll0w4n14!

P.S. Jeżeli kogoś zastanawia od czego mi zależność od biblioteki SEH_obones, to jest ona po to żeby wyjątki nieraisowane przez FPC działały także w DLL. Nie jest ona potrzebna do działania więc nie wrzucam :P .

0

Twój kod w haxlib.zip na pewno się mi przyda to sobie go zachowałem, bo nie będę musiał przyszłościowo kombinowac z injectowaniem dllki pod Lazarusem. Wiem, że mój kod niestety bez przeróbek nie mógł zadziałać pod Lazarusem, ze względu na użycie afxcodehook.pas. Ale wydawało się mi, że sam kod dllki, powinien skompilować się i pod Lazarusem. Bo wszystko, co w nim jest zawarte to czyste WinAPI. A na Twój kod spojrzałem tylko przez chwilę. Później jeszcze będę go sobie analizował dokładniej. Bo widzę tam jakieś ParseKey i pewnie rzeczy zrobione jeszcze po Twojemu. Ale ważne, że działa i sobie poradziłeś. Oczywiśćie w przypadku gdy zależy nam na injekcji tylko w 32 bitowy proces i posiadamy Delphi 7 oraz chcemy aby wynikowy exec był jak najmniejszy to kompilujemy wszystko pod wspomnianym środowiskiem z wykorzystaniem http://kolmck.net/sys/SysDcu7.zip :) Bo @-321oho skorzystałeś z SysUtils przez co, nawet za pewne po stripowaniu exek i dllka nieco "puchną". Ale do źródeł modułów LCL Lazarusa również jest dostęp, więc można poświecić chwilę i spróbować "wypruć" sobie z tego modułu przydatne nam fragmenty kodu.

EDIT: a co do Twojego kodu podpinającego się pod okno, to nie wiem co tam jest nie tak, bo metoda trochę dziwna. Ja zawsze po ustaleniu na pewno głownego okna obcej aplikacji, w kodzie wstrzykiwanej w proces dllki robię wszystko z użyciem SetWindowLong i parametrem GWL_WNDPROC. Nie moge teraz wygooglowac swojego wcześniej odpowiedzi gdzie zrobiłem znowu coś za kogoś. Może nawet i Google nie chce tego pamiętać jak człowike postępuje źle, robiąc krzywdę leniom :) Dlatego wrzucam pliki jeszcze raz w archiwum block_wmplayer_close.rar. Kod oczywiście kompilowany i pisany pod Delphi 7. Może i są inne metody dobrania się do głownego okna aplikacji, ale ja używam takiego, kiedyś pokazał mi go @kAzek a propos subclassingu w jakiejś odpowiedzi do mojego pytania jak zaczynałem zabawę z WinAPI pod Delphi 7, abym wiedział jak można zmienić zachowanie na przykład w obsłudze wprowadzonych znaków do pół edycyjnych i tym podobnych. Zresztą jest to chyba metoda zgodna z tym co opisuje MSDN dla tej funkcji.

0

@-321oho, odezwij się na pw, może dojdziemy do konsensusu, jak załapie to wystawisz na forum, ja do Ciebie mam też kilka pytań

1 użytkowników online, w tym zalogowanych: 0, gości: 1