Programme fernsteuern - Fernsteuerung anderer Progamme mit Mirana durch Simulation von Tastatureingaben und Mausklicks sowie Windows-Nachrichten, DDE und COM.
Durch das Fernsteuern anderere Programme kann man sich viel Klickarbeit ersparen, vor allem wenn viele Dateien in immer gleichen Weise bearbeitet werden sollen oder wenn bei einer Anwendung, die eigentlich im Hintergrund arbeiten soll, immer wieder nervige Dialoge aufpoppen, die immer gleich beantwortet werden müssen. Mirana (mirana.de.to, sites.google.com/site/miranasoft/ oder miranasoft.jimdo.de) enthält dazu einige spezielle Funktionen, die beim Steuern anderer Programme sehr hilfreich sind.
Folgende Möglichkeiten sind denkbar:
- Simulation der Tastatur-Eingaben und Mausaktionen
- Benutzen einer eingebauten Fernsteuerschnittstelle wie DDE oder OLE bzw. COM.
Die zweite Möglichkeit soll hier nur kurz gestreift werden,
da längst nicht jedes Programm eine solche Möglichkeit bietet und die
Beschreibung der Schnittstelle oft schwer zu finden oder gar nicht öffentlich zugänglich
ist. So kann man etwa mit folgendem Mirana-Befehl eine Seite im
Internetexplorer öffnen:
DdeExecute "IExplore","WWW_OpenURL",
"www.wikipedia.de", 5000
Folgende Befehle erhöhen in der in Excel geöffenten Datei
Mappe1.xls in Tabelle1 den benannten Wert „Name1“ um eins:
StrDef str,100
DdeRequest
"Excel","[Mappe1.xls]Tabelle1","Name1",str,100
DdePoke
"Excel","[Mappe1.xls]Tabelle1","Name1",#,(Val(str,2)+1)
Ohne Kenntnis einer Schnittstelle funktioniert dagegen die Simulation von Tastatur-Eingaben und
Mausaktionen sowie Steuerung über Windows-Nachrichten. Allerdings kann nach
einer Versionsänderung eine Anpassung erforderlich sein, wenn dabei die
Bedienoberfläche geändert wurde.
Dabei führt man mit dem Programm einfach die gewünschten
Aktionen aus, bemüht sich dabei aber, möglichst nur die Tastatur zu benutzen.
Sind in einem Menü rechts vom Menütext Tastenbefehle angegeben, sollte man diese
verwenden. Andernfalls sollte man schauen, ob einzelne Buchstaben unterstrichen
sind und kann diese dann zusammen mit der Alt-Taste verwenden. Wenn in einem
Menü oder einem Dialog keine Buchstaben unterstrichen sind, hilft oft, die
Alt-Taste kurz drücken und wieder loslassen. Hilft das nicht, kann man es mit
dem ersten Buchstaben des Menütextes versuchen (ev. mehrfach, gefolgt von der
Eingabetaste), oder man bewegt sich mit TAB, Shift+TAB oder den Kursortasten
durch das Menü oder den Dialog und drückt an der richtigen Stellen die
Eingabetaste oder die Leertaste. Bei Karteikarten ist auch Strg+TAB oder
Shift+Strg+TAB möglich, um zwischen den Reitern zu wechseln.
Damit man sich die Eingaben nicht alle merken muss, ist ein
sog. Keylogger sehr nützlich, welcher alle Tastendrücke aufzeichnet. Dazu habe
ich ein kleines Mirana-Makro namens Keylogger.mac geschrieben (steht in Mirana.zip im Verzeichnis Makros). Dies taugt zwar
weniger zu Spionagezwecken, weil es sich keine Mühen macht, seine Tätigkeit zu
verbergen. Dafür kommt es ohne Installation und Treiber aus. Außerdem werden
auch alle Mausklicks aufgezeichnet, und zwar mit der genauen Position des
Mauszeigers und Informationen über das Fenster, in dem der Mausklick landet und
über dessen Elternfenster. Dies ist sehr nützlich, wenn eine reine
Tastaturbedienung nicht möglich ist. Aber auch bei Tastatureingaben muss man dafür
sorgen, dass sie im richtigen Fenster landen oder warten, bis ein Fenster geöffnet
oder geschlossen wurde. Dazu braucht man Informationen wie Fenstertitel oder
Klassennamen, um das richtige Fenster zu finden. Das gleiche gilt auch, wenn
man einem Fenster oder Steuerelement eine Nachricht schicken will (hier ist
auch die ID wichtig).
Man startet also Keylogger.mac innerhalb von Mirana oder
startet direkt das kompilierte Keylogger.exe und klickt dann auf „Start“. Dann
startet man das fernzusteuernde Programm oder holt es in den Vordergrund. Als
erstes sollte man die Titelleiste des Programms anklicken (wenn es keine
Titelleiste hat, dann irgendwo ins Programmfenster), damit der Keylogger die
Fensterinformationen aufzeichnet. Dies sollte man auch bei jedem im Verlauf der
Sitzung neu geöffneten Fenster oder Dialog tun. Dann führt man die gewüschte
Aktionen durch, wobei die Menüs mit der Tastatur bedient werden sollten. Bei
den Dialogen kann man beide Varianten probieren, also Tastaur- und
Maussteuerung, um dann später die einfachere oder effizientere und sicherere
Variante zu wählen zu können.
Sobald alle Aktionen aufgezeichnet wurden, klickt man im
Keylogger auf „Stop“. Es öffnet sich dann ein Notepad-Fenster mit der erzeugten
Log-Datei. Hier ist zu beachten, dass jeder Mausklick in einer eigenen Zeile
steht, während die Tasten nacheinander in einer Zeile stehen, bis nach 80
Spalten ein Zeilenumbruch erfolgt. Das genaue Format der Aufzeichnung steht im
Kopfbereich der Datei Keylogger.mac (mit Mirana.exe oder einem beliebigen
Texteditor öffnen, z. B. Notepad).
Nun startet man Mirana.exe und erzeugt mit „New“ ein neues
Fenster für das Fernsteuermakro.
Als erstes muss das Makro nun prüfen, ob das zu steuernde
Programm schon läuft. Dazu benutzen wir die Funktion FindChildWindow. Diese
Funktion kann anhand des Titels und/oder des Klassennamens jedes in Windows
geöffnete Fenster finden, egal, ob es sich um ein sog. Toplevel-Fenster oder um
ein in ein anderes Fenster eingebettetes Kindfenster handelt. Da das
Hauptfenster eines Programms immer ein Toplevel-Fenster ist, ist der erste
Parameter -1. Die Parameter 2 und 3 sind Fenstertitel und Klassenname, welche
man aus den Keylogger-Einträgen hinter „window=“ und „class=“ entnehmen kann.
Gibt man für einen der beiden Parameter "NULL"
ein, dann kann der entsprechende Name beliebig sein. Dies macht Sinn, wenn z.
B. der Titel Zusatzinformationen enthält, die jedesmal anders sein können.
Die Funktion FindChildWindow gibt ein Handle des Fensters
zurück, wenn es existiert, sonst 0. Dieses Handle übergibt man der
Windows-API-Funktion SetForegroundWindow, und das Fenster in den Vordergrund zu
holen. Dadurch erreicht man, dass nachfolgende Tastatureingaben über dem Befehl
PostKeyString in diesem Fenster landen. Existiert das Fenster noch nicht, muss
man das Programm mit dem Befehl Exec starten und warten, bis das Fenster
erscheint.
Hier ist ein sehr einfaches Beispiel für eine Fernsteuerung
des auf jedem Windowsrechner vorhandenen Programms Notepad. Dabei soll einfach
ein neuer Tagebucheintrag zu einer Datei hinzugefügt werden:
sFile=!Macropath+"Mein
Tagebuch.txt"
if not FileExist(sFile)
WriteFile
sFile,"Mein Tagebuch"+$13+$10+"-------------"+$13+$10
CloseFiles
endif
hWndMain = FindChildWindow(-1, "Mein Tagebuch.txt
- Editor", "Notepad")
if hWndMain=0
Exec "notepad "+sFile
Loop
;warten, bis Fenster erscheint
hWndMain = FindChildWindow(-1, "Mein Tagebuch.txt
- Editor", "Notepad")
if (hWndMain<>0)
BreakLoop
Pause 50
EndLoop
endif
Decl SetForegroundWindow "i=i,user32.dll"
SetForegroundWindow(hWndMain)
PostKeyString "",0x223 ;[lctrl-0x23(end)] -> ans Textende
sDatum=!Time
sDatum[10]=0 ;nur Datum, keine Uhrzeit
sDatum+=":"+$10 ;: und neue Zeile hinzufügen
PostKeyString
$10+sDatum
PostKeyString
"Keine besondere Vorkommnisse."+$10
PostKeyString
"ds",-4 ;Speichern mit Alt+D,S
End
So könnten die entsprechenden Keylogger-Einträge ausgesehen
haben:
[0x01(lmouse)]{Pos=622,255, window="Mein
Tagebuch.txt - Editor", class="Notepad",
Pos=361,240,
ID=-1064044752}
[lctrl-0x23(end)]17[0xbe(.)]04[0xbe(.)]2012[rshft-0xbe(.)][0x0d(ent)][rshft-K]ei
ne besondere
[rshft-V]orkommnisse[0x0d(ent)][lalt-d][lalt-s]
Informationen zu den Mirana-Befehlen und -Funktionen erhält
man, indem man im Mirana-Editorfenster das entsprechende Wort anklickt und F1
drückt. Windows-API-Funktionen wie SetForegroundWindow oder SetCursorPos kann
man dagegen bei Google eingeben, ev. zusammen mit „msdn“. Solche Funktionen
müssen vor der ersten Verwendung in mit dem Befehl Decl deklariert werden. Eine Sammlung mit wichtigen Deklarationen
ist in WindowsApi.mac enthalten.
Übersicht der wichtigsten Befehle:
FindChildWindow(hParentWnd, Title, Class): sucht ein Fenster mit einem bestimmten Titel und/oder Klassenamen und gibt ein Handle des Fensters zurück, wenn es existiert, sonst 0. Ist hParentWnd=-1, so werden nur Toplevel-Fenster gesucht, ist hParentWnd=0, werden alle Fenster durchsucht, andernfalls werden nur die Kindfenster von hParentWnd sowie deren Kindfenster usw. durchsucht. Wird bei einem der beiden Namen Title oder Class "NULL" eingegeben, dann spielt dieser Name keine Rolle (Class kann auch ganz weggelassen werden). Mit einem vierten Parameter (Skip) kann man eine gewisse Anzahl Treffer überspringen, falls es mehrere Fenster mit dem Suchkriterium gibt, dem Fenstertitel kann man danach mit !StrResult abfragen.
PostKeyString Text: Simuliert die Eingabe
eines Textes über die Tastatur. Der Text kann (in Anführungszeichen) beliebige
Zeichen enthalten (Anführungszeichen verdoppeln!), auch Zeichen, die nicht auf
der Tastatur stehen – die entsprechende Tastenkombinationen werden passend
simuliert.
PostKeyString Text, opt mit negativem opt: Simuliert
die Eingabe eines Textes mit einer oder mehreren dabei ständig gedrückt
gehaltenen Umschalttasten. opt=-1: Shift, opt=-2: Strg, opt=-4: Alt, die Werte
können addiert werden.
PostKeyString "",keycode: Simuliert
den Druck einer einzelnen Taste über seinen Keycode (1-255, siehe Mirana-Hilfe
von !KeyStat). Auch Mausklicks können so simuliert werden: Linksklick=1,
Rechtsklick=2, Mausradklick=4. Durch Addieren eines Wertes > 255 können
zusätzlich Umschalttasten gedrückt werden oder die Taste wird nur
niedergedrückt oder nur losgelassen, siehe Mirana-Hilfe.
SetForegroundWindow
(hWnd): Bringt das Fenster mit dem Handle hWnd in den Vordergrund
Deklaration:
Decl SetForegroundWindow "i=i,user32"
SetCursorPos(x, y): Setzt den Mauszeiger auf die Bildschirmposition
x, y
Deklaration: Decl SetCursorPos
"l=ll,user32"
GetWindowRect(hWnd,
vRect): Abfrage von Position und Größe eines Fensters hWnd. Der Vektor vRect
muss zuvor mit vRect=Vector(4,4) erzeugt
werden. Dabei sind die Werte vRect[0] bis vRect[3] die Rechteckpositionen
Links, Oben, Rechts, Unten.
Deklaration:
Decl GetWindowRect "i=ip,user32"
Als Beispiel Mausklick auf die Mitte von Button hButt im
aktiven Fenster:
vRect=Vector(4,4)
GetWindowRect(hButt,vRect)
SetCursorPos((vRect[0]+vRect[2])/2,
(vRect[1]+vRect[3])/2)
PostKeyString
"", 1 ;Linksklick
SendMessageA
(hWnd, Msg, WPar, LPar): Sendet die Nachricht Msg an das Fenster mit dem Handle
hWnd
Deklaration: Decl
SendMessageA "i=lllp,user32"
Die wichtigsten Nachrichten:
WM_LBUTTONDOWN = 0x0201
WM_LBUTTONUP =
0x0202
WM_KEYDOWN = 0x0100
WM_KEYUP = 0x0101
WM_CHAR = 0x0102
;z.B. SendMessageA(hEdit, WM_CHAR, 'A', 3) -> "AAA"
WM_COMMAND = 0x0111
;z.B. SendMessageA(hDlg,WM_COMMAND,1,0)
; -> Klick auf Button mit ID=1 in Dialog hDlg
Als Beispiel Mausklick auf Button hButt im Fenster hPopup:
hButt=FindChildWindow(hPopup, "Download
fortsetzen", "Button")
WM_LBUTTONDOWN = 0x0201
WM_LBUTTONUP =
0x0202
x=4, y=4 ;Klickposition
relativ zur Buttonposition
SendMessageA(hButt, WM_LBUTTONDOWN, 0, x or
(y<<16))
SendMessageA(hButt, WM_LBUTTONUP, 0, x or
(y<<16))
PostMessageA
(hWnd, Msg, WPar, LPar): Wie SendMessageA, allerdings kehrt diese Funktion
sofort zurück, und nicht erst, wenn die Nachricht bearbeitet wurde. Ungeeignet,
wenn Werte zurückgegeben werden sollen, zwingend nötig beim Öffnen von modalen
Dialogen (SendMessageA würde hier hängen, bis der Dialog wieder geschlossen
wird).
Deklaration:
Decl PostMessageA "i=lllp,user32"
GetWindowTextA(hWnd,
sText, MaxSize): Abfragen des Fenstertextes.
Deklaration:
Decl GetWindowTextA "i=i$i,user32"
Beispiel:
StrDef sTxt,400
GetWindowTextA(hWnd, sTxt, 400)
SetWindowTextA(hWnd,
sText): Ändern eines Fenstertextes.
Deklaration:
Decl SetWindowTextA "i=is,user32"
IsWindow(hWnd):
Abfrage, ob hWnd noch ein gültiges Fensterhandle ist.
Deklaration: Decl IsWindow "l=l,user32"
Steuerung per Mausklicks und Nachrichten
Wenn man mit der Steuerung per Tastatur nicht alle Funktionen erreicht, kann man auch den Mauszeiger mit SetCursorPos positionieren und mit PostKeyString "",1 einen Mausklick erzeugen. Die richtige Position kann man aus dem Keylogger-Log auslesen ([0x01(lmouse)]{Pos=…). Um aber unabhängig von der aktuellen Fensterposition zu werden, kann man die Position mit GetWindowRect abfragen. Da im Keylogger-Log auch die Fensterpositionen aufgezeichnet sind, kann man sich leicht die richtige Kursorposition ausrechnen.
Falls es die Bedienoberfläche die Standardelemente von
Windows benutzt, kann man diese auch durch Senden einer Windows-Nachricht
steuern. Dazu braucht man zwar mehr Vorwissen darüber, welches Element auf
welche Nachricht reagiert, aber dafür muss das entsprechende Fenste nicht aktiv
sein.
Das ist dann wichtig, wenn das zu steuernde Programm im
Hintergrund arbeiten soll, wärend der Benutzer mit einem anderen Programm
arbeitet. Würden immer wieder irgend welche Fenster in den Vordergrund kommen,
wärend der Benutzer gerade tippt oder mit der Maus arbeitet, würden die
Eingaben im falschen Fenster landen und die Fernsteuerung durcheinanderbringen.
Windows-Nachrichten werden dagegen immer direkt an ein bestimmtes Fenster
geschickt, unabhängig davon, ob es aktiv ist, oder nicht.
Beispiel:
hDlg= FindChildWindow(0, "Schriftart",
"#32770")
hButt=FindChildWindow(hDlg, "OK",
"Button")
WM_LBUTTONDOWN = 0x0201
WM_LBUTTONUP =
0x0202
WM_KEYDOWN = 0x0100
WM_KEYUP = 0x0101
Decl SendMessageA "i=lllp,user32"
SendMessageA(hButt, WM_LBUTTONDOWN, 0, 0)
SendMessageA(hButt, WM_LBUTTONDOWN, 0, 0)
SendMessageA(hButt, WM_LBUTTONUP, 0, 0)
Der letzte Parameter in
SendMessageA (hier 0), legt fest, wo auf dem Button
der Klick landet: Lpar = x + (y<<16). 0 wäre dann die linke odere Ecke,
das ist ok. Wichtig ist nur, dass der Klick nicht außerhalb landet.
Statt der Maus kann man auch die Leertaste betätigen, man
benötigt aber noch den „keyboard scan code“ der Leertaste (0x39):
SendMessageA(hButt, WM_KEYDOWN, ' ', 0x390001)
SendMessageA(hButt, WM_KEYDOWN, ' ', 0x390001)
SendMessageA(hButt, WM_KEYUP, ' ', 0x390001)
Der zweimalige Aufruf von WM_LBUTTONDOWN oder WM_KEYDOWN
macht es zuverlässiger – immer wenn der Button schon ausgewählt war,
funktionierte es bei mir sonst nicht, warum auch immer.
Die letzten drei Zeilen kann man sich sparen, wenn man
gleich folgendes aufruft:
FindChildWindow(hDlg, "OK",
"Button", 0x01000000)
Der letzte Parameter 0x01000000
führt zu einer Leertastennachricht an den gefundenen Button.
Fenster mit der Klasse "Edit" reagieren dagegen
auf die Nachricht WM_CHAR. Folgende Zeilen schicken "AAA" an das
Fenster:
hEdit=FindChildWindow(hWndMain, "",
"Edit")
WM_CHAR = 0x0102
SendMessageA(hEdit, WM_CHAR, 'A', 3)
An eine Dialogbox (Klasse "#32770") kann man auch
eine WM_COMMAND-Nachricht schicken, wobei Wpar die ID des zu aktivierenden
Buttons ist. In einer Messagebox ist typischerweise "OK"=1, "Abbrechen"=
2, "A&bbrechen"=3, "Wieder&holen"=4, "&Ignorieren"=5,
"&Ja"=6, "&Nein"=7 (Das & kennzeichnet
unterstrichene Zeichen). Die richtige ID kann man aus dem Keylogger-Log
auslesen, wenn man auf den jeweiligen Button klickt.
Die folgenden Zeilen beantworten eine Ja/Nein-Messagebox mit
der Titelzeile "Bestätigung" mit Ja:
hMgsBox =
FindChildWindow(0, "Bestätigung", "#32770")
WM_COMMAND =
0x0111
SendMessageA(hMgsBox,WM_COMMAND,6,0)
Alternative:
FindChildWindow(hMgsBox, "&Ja", "Button", 0x01000000)
Wenn es nur einen Dialog mit einem Ja-Button gibt, kann der
erste Parameter von FindChildWindow auch 0 sein. Dann wird der erste gefundene
"&Ja"-Button betätigt:
FindChildWindow(0,
"&Ja",
"Button", 0x01000000)
Hier ist zu beachten, dass die Schreibweise exakt stimmen
muss, also korrekte Groß-/Kleinschreibung und & für unterstichene Zeichen,
so wie der Name auch im Keylogger-Log auftaucht.
Auch Menüs können oft per Nachricht geöffnet werden,
allerdings braucht man dann einen geeigneten Resource-Editor wie z.B. ResHacker.exe,
um die Menüs aus der Exe herauszulesen und so die passende ID herauszufinden
(in ResHacker die Zahl hinter dem Menütext). Allerdings sollte hier
PostMessageA benutzt werden – falls nämlich mit der Nachricht ein modaler
Dialog geöffnet wird, würde SendMessageA hängen, bis der Dialog wieder
geschlossen wird, und dies könnte dann nur noch der Benutzter tun.
Beispiel:
Decl PostMessageA "i=lllp,user32"
WM_COMMAND = 0x0111
ID=57696 ;Menü
Format/Font…
hWndMain = FindChildWindow(-1, "NULL",
"WordPadClass")
PostMessageA(hWndMain,WM_COMMAND,ID,0)