Ein Treeview im Speicher
Der folgende
Artikel bezieht sich auf die Betriebsysteme Windows2000/XP. Die hier
aufgeführten Tests wurden unter Windows2000 unter einem
Administratoraccount durchgeführt.
Für das
Nachvollziehen der hier gemachten Aussagen sind folgende
Zusatzprogramme erforderlich:
- Tasks
and Token
- Eine
Version von XProfan.
- Profan2Cpp
Als Ausgangspunkt für unsere Betrachtungen soll einmal
folgender Quelltext herhalten:
Declare Treeview3&,Tv_insert#,Tv_text$,Parent&,Child&,Neu& |
Windowstyle 31 |
Windowtitle "Treeviewtest" |
Window 0,0-640,440 |
Dim Tv_insert#,48 |
Treeview3&=@Control("SysTreeView32","",$50A10832,10,100,600,300,%Hwnd,2603,%Hinstance) |
Clear Tv_insert# |
Let Tv_text$="Root" 'Der Text im Label. |
Long Tv_insert#,0=0 'Der Eintrag ist der erste Eintrag im Treeview. |
Long Tv_insert#,4=$Ffff0002'Der Eintrag kommt an den Schluß. |
Long Tv_insert#,8=$0001'Nur Text, keine weiteren Angaben zum Eintrag. |
Long Tv_insert#,24=@Addr(Tv_text$) 'Adresse des Textes des Eintrags. |
Long Tv_insert#,28=$Ffff'Maximale Länge des Eintrags. |
@Sendmessage(Treeview3&,$1100,0,Tv_insert#)'Message zum Einfügen eines Eintrages. |
Print "Handle des angezeigten Treeviews: "+@Str$(Treeview3&) |
Treeview3&=@Val(@Input$("Handle eines Treeviews eingeben:","Handle",@Str$(Treeview3&))) |
Let Parent&=@Sendmessage(Treeview3&,$110A,0,0) |
Print "Handle des Rootitems des eingegebenen Treeviews: "+@Str$(Parent&) |
Child&=@Sendmessage(Treeview3&,$110A,$4,Parent&) |
Print "Handle des ersten Kinditems des eingegebenen Treeviews: "+@Str$(Child&) |
Print "Zeit="+@Time$(0)'Daten werden zur Kontrolle ausgegeben. |
Clear Tv_insert# |
Tv_text$=@Time$(0)+" Dies ist ein Treeview-Test! Dies ist ein Treeview-Test! " |
Tv_text$=Tv_text$+"Dies ist ein Treeview-Test! Dies ist ein Treeview-Test! " |
Tv_text$=Tv_text$+"Dies ist ein Treeview-Test! Dies ist ein Treeview-Test! " |
Tv_text$=Tv_text$+"Dies ist ein Treeview-Test!" |
Long Tv_insert#,0=Parent& 'Der zweite Eintrag wird unter dem Rooteintrag erstellt. |
Long Tv_insert#,4=$Ffff0002 'Der Eintrag kommt an den Schluß. |
Long Tv_insert#,8=$0001'Nur Text, keine weiteren Angaben zum Eintrag. |
Long Tv_insert#,24=@Addr(Tv_text$) 'Adresse des Textes |
Long Tv_insert#,28=$Ffff'Maximale Länge des Eintrags. |
Neu&=@Sendmessage(Treeview3&,$1100,0,Tv_insert#) 'Message zum Einfügen eines Eintrages. |
While 0=0 |
Waitinput |
Wend |
Dispose Tv_insert# 'Müll wegräumen... |
Bitte einfach
immer OK drücken und vorerst im Eingabefeld nichts verändern.
Bei mir sieht die Geschichte nach einem Doppelklick auf den Eintrag
ROOT dann so aus:
Uns soll vorerst einmal nicht der Rooteintrag, sondern der
Eintrag mit der Uhrzeit darunter interessieren. Das von der Message
TVM_INSERTITEM zurückgegebene Handle dieses Eintrags lautet bei
mir 36687824.
Nun kommt wieder Tasks and Token ins Spiel.
Nachdem wir im Treeview von Tasks and Token den gerade
erzeugten Prozess angeklickt haben, machen wir mal wieder das
altbekannte Spiel und tun ganz frech so, als wäre das Handle
eine Adresse. Dazu lassen wir uns die Heaps des Prozesses listen -
wenn man das Programm nicht mit Profan2Cpp compiliert, kann das
Listing etwas nachdem die Heapblockadresse die dem Handle entspricht
(36687824) erreicht wurde, mit der Taste 'PAUSE' abgebrochen werden.
Nun suchen wir mal die Heapblockstartadresse, die dem Handle des
Treevieweintrags entspricht und lassen uns den Inhalt des Blocks als
dezimale Doublewords anzeigen:
Folgendes wird bei mir angezeigt:
X1=36685936 |
X2=0 |
X3=0 |
X4=36688672 |
X5=0 |
X6=0 |
X7=0 |
X8=131071 |
X9=-1412628479 |
X10=47382540 |
X11=393242 |
X12=524544 |
X1 ist hier scheinbar das Handle des
übergeordneten Eintrags, das ist leicht ersichtlich. Nun schauen
wir uns mal X4 an. Wir tun wieder so, als wäre das Doubleword X4
eine Adresse (hoffentlich wirds nicht langweilig) und schauen nach,
ob wir einen Heapblock finden, dessen Startadresse dieser Adresse
(bei mir 3668872) entspricht und lassen uns dann den Inhalt des
Heapblocks als String darstellen. So sieht's bei mir aus:
Wie man sieht, hier steht der Text des Treevieweintrages. Das
Handle, das wir durch das Senden der Message TVM_INSERTITEM und das
damit verbundene Erstellen eines neuen Treevieweintrags erhalten, ist
also also das Handle des Heapblocks, in dem der Eintrag steht - und
das Handle entspricht genau der Startadresse des Blockes. Eingentlich
sieht das ja ganz unspektakulär aus, ist es aber nicht - denn
was wichtig ist, ist die Tatsache daß ich hier durch einfaches
senden von Messages eine Adresse eines Speicherbereiches - vielleicht
auch innerhalb eines fremden Prozesses - erhalte. Eine weitere, sehr
interessante Geschichte ist, daß sich der Heapblock mit dem
Text des Items nur wenig oberhalb dieser Adresse befindet.
Jetzt
wird etwas phantasiegeladen: Wenn es nun möglich wäre,
einen Treevieweintrag in einem fremden Prozess zu erzeugen, würde
mir mit der Erzeugung gleich eine Adresse mitgeliefert werden. Statt
Text könnte ich beim Erzeugen eine DLL mit Quelltext übertragen
und die beim Senden der Message TVM_INSERTITEM erhaltene Adresse
könnte ich als Ausgangspunkt dafür nehmen, den in der DLL
enthaltenen Quelltext (mittels WM_TIMER, eine Sache die ich schon
erfolgreich mit einem Multiedit durchgeführt habe) anzusprechen
- und das ohne irgendwelche Schreib- oder Leserechte auf den Prozess
zu haben!
Aber geht das wirklich? Mal schauen...
Dazu müssen
wir einfach den schon bekannten Quelltext zweimal starten. Beim
ersten Start klicken wir beim Eingabefeld auf OK, beim zweiten Start
geben wir das Handle des Treeviews des ersten Prozesses (nicht des
Eintrages) ein und bestätigen dann mit OK: Zwischen den Starts
der beiden Prozesse müssen unbedingt einige Minuten liegen, das
ist sehr wichtig. Das hier zeigt bei mir dann der zuerst gestartete
Prozess an:
Mmh - wenn man nicht ganz genau hinsieht, war unser vorhaben
erfolgreich. Was bei näherer Betrachtung aber auffällt -
die Uhrzeiten der beiden Einträge müßten ja
unterschiedlich sein, sind sie aber nicht! Was ist genau geschehen?
Von mir gibt's hier folgende Erklärung: Was beim Senden einer
Message übertragen wird sind immer nur vier Doublewords - in
unserem Fall wurde hier als vierter Parameter ein Pointer, also die
Startadresse der Struktur TV_INSERT#, übertragen. Der Empfänger
der Message (Prozess Nummer eins) bezieht die mit der Message
übertragene Adresse komplett auf seinen Adressbereicht. Da es
sich bei den beiden Prozessen um das gleiche Programm handelt, steht
an dieser Adresse ebenfalls noch die Struktur TV_INSERT# - was in
TV_TEXT$ steht, unterscheidet sich aber.
Fazit:
- a)
Messages übertragen keine Strukturen sondern nur Adressen, die
sich auf den jeweils aktuellen Prozess beziehen.
- b) Das Handle
eines Treeview Eintrages entspricht dessen virtueller Adresse im
Speicherbereich des jeweiligen Prozesses. Einträge in Treeviews
können mittels dieser Adresse direkt im Speicher geändert
werden. Das Senden von Messages kann mittels solcher direkter
Schreibzugriffe recht einfach umgangen werden.
Wie aus
Abschnitt b) ersichtlich ist, stellt die in Treeviews verwendete
Rückgabe eines Handles bei TVM_INSERTITEM und TVM_GETNEXTITEM
ein meiner Meinung nach nicht zu unterschätzendes
Sicherheitsproblem da, das beim Schreiben von sicherheitsrelevanten
Anwendungen unbedingt berücksichtigt werden sollte!